From c9b4f0bede7fd0c6f60aa2ffcf0bc72a3bd43dfa Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 18 Oct 2026 12:19:39 +0900 Subject: [PATCH] =?UTF-8?q?fix(B06=20=EC=8B=AD=EC=9E=90=EC=84=A0):=20?= =?UTF-8?q?=EC=8B=AD=EC=9E=90=EC=84=A0=20=3D=20=ED=98=95=EC=83=81=EA=B3=BC?= =?UTF-8?q?=20=ED=95=9C=20=EB=86=92=EC=9D=B4(=EC=B8=A1=EC=A0=90=20?= =?UTF-8?q?=EC=84=A4=EA=B3=84=20=EB=86=92=EC=9D=B4)=20=C2=B7=20=EC=9B=90?= =?UTF-8?q?=EC=A7=80=EB=B0=98=20=EC=8B=AD=EC=9E=90=EC=84=A0=20=C2=B7=20?= =?UTF-8?q?=ED=8F=89=EB=A9=B4=20=EC=B4=88=EC=95=88=20=EA=B3=84=ED=9A=8D?= =?UTF-8?q?=EC=84=A0=20=ED=91=9C=EB=B3=B8=EB=8F=84=20=EC=B4=88=EC=95=88=20?= =?UTF-8?q?=EC=A2=85=EB=8B=A8=20(413-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 카드 · B05 오른쪽 판 · [상세] 창 십자선 · 계획 상자 = section.design.design_elevation_m · 설계 없을 때만 종단 표본 - 원지반 십자선(center_z · 원지반 색 · 다른 무리) 먼저 그림 · 겹치면 보라 위 · 확대 보정 둘 다 - [상세] 표 원지반고 = 원지반 십자 · 절성토고 = 둘 다 - 시험 318 · 150 · 164 고쳐 씀 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG --- .../logic/B05_Profile_Pipeline_Detail.ts | 5 +- B06_Section/B06_Section_UI_Cross_Detail.ts | 17 ++-- .../B06_Section_UI_Cross_Detail_Pick.ts | 8 +- B06_Section/B06_Section_UI_Cross_View.ts | 9 +- .../B06_Section_UI_Cross_View_Frame.ts | 58 +++++++------ B06_Section/B06_Section_UI_Section_View.ts | 6 +- .../B06_Section_UI_Style_Cross_Areas.css | 9 +- .../B06_Section_UI_Style_Cross_Detail.css | 1 + resources/tester/helper_150_detail_pick.cjs | 10 ++- resources/tester/helper_164_detail_zoom.cjs | 10 ++- resources/tester/helper_318_cross_card.cjs | 82 +++++++++++++++++++ resources/tester/test_150_detail_pick.py | 5 +- resources/tester/test_164_detail_zoom.py | 4 +- resources/tester/test_318_cross_card.py | 40 +++++++-- 14 files changed, 210 insertions(+), 54 deletions(-) diff --git a/B05_Profile/logic/B05_Profile_Pipeline_Detail.ts b/B05_Profile/logic/B05_Profile_Pipeline_Detail.ts index 46372b979..ecd3a6f1b 100644 --- a/B05_Profile/logic/B05_Profile_Pipeline_Detail.ts +++ b/B05_Profile/logic/B05_Profile_Pipeline_Detail.ts @@ -24,6 +24,7 @@ import type { PipelineStation, } from "../../A00_Common/design/logic/design_pipeline_types"; import type { ProfileAlignment } from "./B05_Profile_UI_Profile_Alignment"; +import { toDesignProfile } from "./B05_Profile_UI_Profile_Data"; import { stationLabel } from "../../common_util/common_util_station"; import { stationIdOf } from "./B05_Profile_Pipeline_Grade"; @@ -212,7 +213,8 @@ export function stationSection( }; } -/** 묶음 출력 → 상세. 저장 상세의 종단 머리(이름 · 계획선 묶음)는 그대로 · 측점 · 지반 · 횡단은 새 노선. */ +/** 묶음 출력 → 상세. 저장 상세의 종단 머리(이름)는 그대로 · 측점 · 지반 · 횡단 · 계획선 표본은 새 노선 + * (십자선 대체 높이도 초안 종단 · 413). */ export function draftSectionDetail( saved: SectionDetailResponse, ground: GroundOutput, @@ -242,6 +244,7 @@ export function draftSectionDetail( })), stations: sections.map(({ samples: _s, design: _d, preview: _p, ...head }) => head), profile_alignment: profile, + design_profiles: [toDesignProfile(profile, saved.longitudinal.design_profiles?.[0])], }, cross_sections: sections, balloon_offsets: saved.balloon_offsets, diff --git a/B06_Section/B06_Section_UI_Cross_Detail.ts b/B06_Section/B06_Section_UI_Cross_Detail.ts index 1cd162b62..d5aee6fbc 100644 --- a/B06_Section/B06_Section_UI_Cross_Detail.ts +++ b/B06_Section/B06_Section_UI_Cross_Detail.ts @@ -37,6 +37,7 @@ import { type ZoomPanState, } from "../A00_Common/b_svg_zoom_pan"; import { buildZoomControls } from "./B06_Section_UI_Cross_View_Zoom"; +import { unscaleAt } from "./B06_Section_UI_Cross_View_Frame"; import { detailHomeView, layerPoints } from "./B06_Section_UI_Cross_Detail_Home"; import { attachDetailTable, designForTable, groupSpan } from "./B06_Section_UI_Cross_Detail_Table"; import { createCrossAxes, IDENTITY_VIEW, type CrossAxesOptions } from "./B06_Section_UI_Cross_Axes"; @@ -209,16 +210,20 @@ export function attachDetailZoom( const [, , width, height] = (clone.getAttribute("viewBox") ?? "").split(/[\s,]+/).map(Number); const axisLayer = clone.querySelector("g.b06-chart__axes"); const renderAxes = axes && axisLayer ? createCrossAxes(axisLayer, axes) : null; + // 십자 중심 = 세로 선 x1 · y1 + 18 · 계획 십자가 첫 보기 중심 · 원지반 십자도 크기 되돌림(413) + const centerOf = (marker: Element | null): [number, number] => { + const line = marker?.querySelector("line"); + return [Number(line?.getAttribute("x1")), Number(line?.getAttribute("y1")) + 18]; + }; const marker = layer.querySelector("g.b06-chart__center"); const markerLine = marker?.querySelector("line"); - const cx = Number(markerLine?.getAttribute("x1")); - const cy = Number(markerLine?.getAttribute("y1")) + 18; + const [cx, cy] = centerOf(marker); + const ground = layer.querySelector("g.b06-chart__ground-center"); + const [gx, gy] = centerOf(ground); const follow = (state: ZoomPanState): void => { renderAxes?.(state); - marker?.setAttribute( - "transform", - `translate(${cx} ${cy}) scale(${1 / Math.max(state.scale, 1e-6)}) translate(${-cx} ${-cy})`, - ); + marker?.setAttribute("transform", unscaleAt(cx, cy, state.scale)); + ground?.setAttribute("transform", unscaleAt(gx, gy, state.scale)); }; follow(initial ?? IDENTITY_VIEW); const plot = { diff --git a/B06_Section/B06_Section_UI_Cross_Detail_Pick.ts b/B06_Section/B06_Section_UI_Cross_Detail_Pick.ts index 577b14652..5b7e907e1 100644 --- a/B06_Section/B06_Section_UI_Cross_Detail_Pick.ts +++ b/B06_Section/B06_Section_UI_Cross_Detail_Pick.ts @@ -2,7 +2,7 @@ * B06_Section_UI_Cross_Detail_Pick.ts * [상세] 수량표 칸 누름 → 그림 복제 안 그 항목 강조(150-4). * - * 칸 키 → 그림 요소: 면적 띠(깍기 · 쌓기) · 사토장 선 · 중심 십자(높이 넷) · 보라 판정 선 구역 + * 칸 키 → 그림 요소: 면적 띠(깍기 · 쌓기) · 사토장 선 · 계획 · 원지반 십자(높이 넷) · 보라 판정 선 구역 * (사면 · 노면 · 측구). 판정 선은 화면 좌표라 십자(x(0)) · `data-px-per-m` 로 m 로 되돌려 * `crossZoneAt` 으로 가름 — 측구 = 단면유형을 성토 양쪽으로 둬도 절토로 남는 자리. * 편책 · 제근 · 표토제거는 늘 빈칸 = 누름 없음(짝 표에서 뺌) · 대상이 그 카드에 없는 칸 = `presentKeys` 밖. @@ -16,10 +16,10 @@ import { crossZoneAt, type CrossZone } from "./B06_Section_UI_Cross_Design_Pick" type LineZone = CrossZone | "ditch" | "cut_slope"; /** 칸 키 → 강조 대상(선택자 = 그 요소 · zone = 그 구역 판정 선). 없는 키 = 누름 없음. */ export const DETAIL_TARGETS: Readonly> = { - ground: { selector: "g.b06-chart__center" }, + ground: { selector: "g.b06-chart__ground-center" }, // 원지반 십자(413) planned: { selector: "g.b06-chart__center" }, - fill: { selector: "g.b06-chart__center" }, - cut: { selector: "g.b06-chart__center" }, + fill: { selector: "g.b06-chart__center, g.b06-chart__ground-center" }, + cut: { selector: "g.b06-chart__center, g.b06-chart__ground-center" }, cut_soil: { selector: "g.b06-chart__area--cut_soil" }, cut_rock: { selector: "g.b06-chart__area--cut_rock" }, embankment: { selector: "g.b06-chart__area--fill" }, diff --git a/B06_Section/B06_Section_UI_Cross_View.ts b/B06_Section/B06_Section_UI_Cross_View.ts index 1384ae987..5cdb8e004 100644 --- a/B06_Section/B06_Section_UI_Cross_View.ts +++ b/B06_Section/B06_Section_UI_Cross_View.ts @@ -344,7 +344,9 @@ export function createCrossSectionCard( } else { const { sourceSamples, maxOffset, elevationMid, displayMax, pixelsPerMeter, heightPx } = metrics; - const hasDesign = designElevation !== undefined && Number.isFinite(designElevation); + // 십자선 · 계획 상자 = 형상과 한 높이(그 측점 설계 높이 · 413) · 설계 없을 때만 넘겨받은 종단 표본 + const designZ = section.design?.design_elevation_m ?? designElevation; + const hasDesign = designZ !== undefined && Number.isFinite(designZ); const valid = sourceSamples.filter(validElevation); const exaggeration = Math.max(verticalExaggeration, 0.1); // 좌표 규약(+offset=좌, -offset=우)을 표준 횡단면도 관례에 맞춘다: 진행방향을 바라보는 @@ -378,7 +380,7 @@ export function createCrossSectionCard( elevationMid, exaggeration, centerZ: section.center_z ?? null, - designZ: hasDesign ? (designElevation as number) : null, + designZ: hasDesign ? (designZ as number) : null, }); renderAxes(cardZoomStates.get(section.station_id) ?? IDENTITY_VIEW); @@ -610,7 +612,8 @@ export function createCrossSectionCard( const scaleCenter = appendCardFrame(svg, plotLayer, { valid, centerX: x(0), - designElevation: hasDesign ? (designElevation as number) : null, + designElevation: hasDesign ? (designZ as number) : null, + groundElevation: section.center_z ?? null, toY: toDisplayY, widthPx, heightPx, diff --git a/B06_Section/B06_Section_UI_Cross_View_Frame.ts b/B06_Section/B06_Section_UI_Cross_View_Frame.ts index 22e364a15..f9b69d206 100644 --- a/B06_Section/B06_Section_UI_Cross_View_Frame.ts +++ b/B06_Section/B06_Section_UI_Cross_View_Frame.ts @@ -15,6 +15,8 @@ export interface CardFrameInput { centerX: number; /** 계획고(없으면 null — 옛 자료는 지반고 폴백). */ designElevation: number | null; + /** 중심 원지반고(`section.center_z` · 없으면 null = 중심에 가장 가까운 지반고). */ + groundElevation?: number | null; /** 표고 → 화면 y(높이 배율 반영). */ toY: (elevation: number) => number; widthPx: number; @@ -34,27 +36,30 @@ export function appendCardFrame( if (!nearest || Math.abs(sample.offset_m ?? 0) < Math.abs(nearest.offset_m ?? 0)) return sample; return nearest; }, null); + const groundElevation = input.groundElevation ?? centerSample?.elevation_m ?? null; // 십자선 = 계획 노선 자리(구 데이터는 지반고 폴백). - const centerElevation = hasDesign ? designElevation : (centerSample?.elevation_m ?? null); + const centerElevation = hasDesign ? designElevation : groundElevation; const centerY = centerElevation !== null ? toY(centerElevation) : heightPx / 2; - const centerMarkerClass = `b06-chart__center-marker${hasDesign ? " b06-chart__center-marker--design" : ""}`; - const marker = svgElement("g", { class: "b06-chart__center" }); - marker.append( - svgElement("line", { - x1: centerX, - y1: centerY - 18, - x2: centerX, - y2: centerY + 18, - class: centerMarkerClass, - }), - svgElement("line", { - x1: centerX - 18, - y1: centerY, - x2: centerX + 18, - y2: centerY, - class: centerMarkerClass, - }), + const cross = (groupClass: string, y: number, lineClass: string): SVGElement => { + const group = svgElement("g", { class: groupClass }); + group.append( + svgElement("line", { x1: centerX, y1: y - 18, x2: centerX, y2: y + 18, class: lineClass }), + svgElement("line", { x1: centerX - 18, y1: y, x2: centerX + 18, y2: y, class: lineClass }), + ); + return group; + }; + // 원지반 십자선(413) — 계획 십자선보다 먼저(겹치면 보라가 위) · 다른 무리(고름 · 강조 선택자 안 닿음) + const groundY = groundElevation !== null ? toY(groundElevation) : null; + const ground = + hasDesign && groundY !== null + ? cross("b06-chart__ground-center", groundY, "b06-chart__ground-center-marker") + : null; + const marker = cross( + "b06-chart__center", + centerY, + `b06-chart__center-marker${hasDesign ? " b06-chart__center-marker--design" : ""}`, ); + if (ground) plotLayer.append(ground); plotLayer.append(marker); svg.append( svgElement("line", { @@ -79,12 +84,17 @@ export function appendCardFrame( class: "b06-chart__axis-label", }), ); - // 도형 레이어 배율을 십자선 중심에서 되돌림 — 자리는 따라가고 길이 18px 은 그대로. - return (scale) => - marker.setAttribute( - "transform", - `translate(${centerX} ${centerY}) scale(${1 / Math.max(scale, 1e-6)}) translate(${-centerX} ${-centerY})`, - ); + // 도형 레이어 배율을 십자선 중심에서 되돌림 — 자리는 따라가고 길이 18px 은 그대로(두 십자 모두). + return (scale) => { + marker.setAttribute("transform", unscaleAt(centerX, centerY, scale)); + if (ground && groundY !== null) + ground.setAttribute("transform", unscaleAt(centerX, groundY, scale)); + }; +} + +/** (cx, cy) 를 축으로 배율을 되돌리는 transform — 카드 · [상세] 창 십자선이 같이 씀. */ +export function unscaleAt(cx: number, cy: number, scale: number): string { + return `translate(${cx} ${cy}) scale(${1 / Math.max(scale, 1e-6)}) translate(${-cx} ${-cy})`; } /** 원지반선 — 끊긴 샘플(무효 표고)마다 폴리선을 나눠 그린다. `toY` = 표고 → 화면 y(배율 반영). */ diff --git a/B06_Section/B06_Section_UI_Section_View.ts b/B06_Section/B06_Section_UI_Section_View.ts index b0b8745f3..dccd69edf 100644 --- a/B06_Section/B06_Section_UI_Section_View.ts +++ b/B06_Section/B06_Section_UI_Section_View.ts @@ -335,13 +335,13 @@ export function createSectionView( applySelection(previous); }; - /** 십자선이 설 계획고 — 세월교 측점은 구체 위 노면이 월류 높이만큼 낮게 앉으므로 - * 그만큼 내려 찍는다(2026-08-30 사용자 확정). 하강량은 백엔드가 계획고를 내려 - * 잡으며 실어 준 값(`design.surface_drop_m`)이고, 원래 노면은 점선이 남긴다. */ + /** 십자선이 설 계획고 = 그 측점 설계 높이(형상과 한 값 · 413). 설계가 없을 때만 종단 표본 — + * 세월교 측점은 월류 높이(`design.surface_drop_m`)만큼 내려 찍는다(2026-08-30 사용자 확정). */ const cardDesignElevation = ( detail: SectionDetailResponse, section: CrossSection, ): number | undefined => { + if (section.design?.design_elevation_m !== undefined) return section.design.design_elevation_m; const planZ = designElevationAt(detail.longitudinal.design_profiles, section.chainage_m); if (planZ === undefined) return undefined; return planZ - (section.design?.surface_drop_m ?? 0); diff --git a/B06_Section/B06_Section_UI_Style_Cross_Areas.css b/B06_Section/B06_Section_UI_Style_Cross_Areas.css index 11a69d08d..b38cfbf9d 100644 --- a/B06_Section/B06_Section_UI_Style_Cross_Areas.css +++ b/B06_Section/B06_Section_UI_Style_Cross_Areas.css @@ -85,10 +85,17 @@ } /* 중심점 고름(356-3) — 십자 자리 투명 원 · 고르면 보라 테두리 · 끌면 좌우. 십자는 누름을 원으로 넘김. */ -.b06-chart__center { +.b06-chart__center, +.b06-chart__ground-center { pointer-events: none; } +/* 원지반 십자선(413) = 원지반선 색 · 계획 십자선 아래(먼저 그림) */ +.b06-chart__ground-center-marker { + stroke: var(--color-chart-1); + stroke-width: 2.5; +} + .b06-chart__center-hit { fill: transparent; stroke: transparent; diff --git a/B06_Section/B06_Section_UI_Style_Cross_Detail.css b/B06_Section/B06_Section_UI_Style_Cross_Detail.css index ced79a277..561f61880 100644 --- a/B06_Section/B06_Section_UI_Style_Cross_Detail.css +++ b/B06_Section/B06_Section_UI_Style_Cross_Detail.css @@ -154,6 +154,7 @@ stroke-width: 9; } +.b06-detail__cad .b06-chart__ground-center.is-active line, .b06-detail__cad .b06-chart__center.is-active line { stroke: var(--color-danger); stroke-width: 3; diff --git a/resources/tester/helper_150_detail_pick.cjs b/resources/tester/helper_150_detail_pick.cjs index ab212774b..9368350c3 100644 --- a/resources/tester/helper_150_detail_pick.cjs +++ b/resources/tester/helper_150_detail_pick.cjs @@ -58,7 +58,9 @@ const matches = (el, selector) => { }; function fakeSvg(ppm, x0) { const center = node("g", ["b06-chart__center"]); + const ground = node("g", ["b06-chart__ground-center"]); // 원지반 십자(413) const els = [ + ground, center, node("line", [], { x1: String(x0) }, center), node("g", ["b06-chart__area", "b06-chart__area--cut_soil"]), @@ -104,7 +106,11 @@ const activeOf = (svg) => { if (el.set.has("is-active") && !el.set.has("b06-chart__design-hit")) names.push( [...el.set].find( - (c) => c.includes("--") || c === "b06-chart__center" || c === "b06-chart__spoil-fill", + (c) => + c.includes("--") || + c === "b06-chart__center" || + c === "b06-chart__ground-center" || + c === "b06-chart__spoil-fill", ), ); return names; @@ -124,6 +130,8 @@ for (const key of [ "embankment", "spoil_fill", "ground", + "planned", + "cut", "ditch_soil", "grading_cut", "grading_fill", diff --git a/resources/tester/helper_164_detail_zoom.cjs b/resources/tester/helper_164_detail_zoom.cjs index 069ed3ee5..6660904f0 100644 --- a/resources/tester/helper_164_detail_zoom.cjs +++ b/resources/tester/helper_164_detail_zoom.cjs @@ -129,14 +129,17 @@ function chart() { const plot = svgElement("g", { class: "b06-chart__plot-layer" }); const center = svgElement("g", { class: "b06-chart__center" }); center.append(svgElement("line", { x1: 319, y1: 124, x2: 319, y2: 160 })); - plot.append(center); + // 원지반 십자(413) — 계획 십자보다 먼저 · 중심 319,200 + const ground = svgElement("g", { class: "b06-chart__ground-center" }); + ground.append(svgElement("line", { x1: 319, y1: 182, x2: 319, y2: 218 })); + plot.append(ground, center); clipGroup.append(plot); svg.append(svgElement("rect"), axes, clipGroup, svgElement("line", { class: "b06-chart__axis" })); - return { svg, axes, plot, center }; + return { svg, axes, plot, center, ground }; } const ticks = (axes) => axes.childNodes.filter((node) => node.tag === "text").map((node) => node.textContent); -const { svg, axes, plot: layer, center } = chart(); +const { svg, axes, plot: layer, center, ground } = chart(); let saved; const handle = attachDetailZoom(svg, AXES, undefined, (next) => (saved = next)); const ticksBefore = ticks(axes); @@ -194,6 +197,7 @@ out.reset = layer.getAttribute("transform"); // 확대 — 십자는 중심에서 배율을 되돌림(크기 그대로) svg.fire("wheel", { clientX: 600, clientY: 300, deltaY: -100 }); out.centerTransform = center.getAttribute("transform"); +out.groundTransform = ground.getAttribute("transform"); out.max = (() => { for (let i = 0; i < 60; i++) svg.fire("wheel", { clientX: 600, clientY: 300, deltaY: -100 }); return saved.scale; diff --git a/resources/tester/helper_318_cross_card.cjs b/resources/tester/helper_318_cross_card.cjs index 7ab54a238..537109667 100644 --- a/resources/tester/helper_318_cross_card.cjs +++ b/resources/tester/helper_318_cross_card.cjs @@ -318,6 +318,88 @@ const out = {}; }; } +// ── 1b. 십자선 둘(413) — 원지반 먼저 · 계획 나중 · 배율 되돌림 둘 다 ───────────── +{ + const { appendCardFrame } = makeLoader( + [ + B06("B06_Section_UI_Cross_View_Frame"), + B06("B06_Section_UI_Section_Common"), + path.join(ROOT, "common_util", "common_util_svg.ts"), + ], + { + "@config/config_frontend": { PIPE_TYPES: [] }, + "../B05_Profile/B05_Profile_UI_IrregularStations": { PIPE_DISPLAY_NAME: {} }, + "@ui/ui_template_locale": { + currentLanguageIndex: 0, + ui_locales: new Proxy({}, { get: (_t, key) => [String(key)] }), + }, + }, + )(B06("B06_Section_UI_Cross_View_Frame")); + const toY = (z) => (900 - z) * 10; + const frame = (designElevation, groundElevation) => { + const plot = new FakeNode("g"); + const scale = appendCardFrame(new FakeNode("svg"), plot, { + valid: [{ offset_m: 0.4, elevation_m: 870, valid: true }], + centerX: 50, + designElevation, + groundElevation, + toY, + widthPx: 480, + heightPx: 300, + }); + return { plot, scale }; + }; + const midY = (group) => Number(group.querySelector("line").attrs.y1) + 18; + const { plot, scale } = frame(878.5, 876.57); + scale(2); + const [groundG, designG] = plot.children; + const bare = frame(null, null).plot; + out.crosshair = { + order: plot.children.map((node) => node.className), + design_y: midY(designG), + want_design_y: toY(878.5), + ground_y: midY(groundG), + want_ground_y: toY(876.57), + design_only_ground: bare.querySelector(".b06-chart__ground-center"), + scaled: [groundG.attrs.transform, designG.attrs.transform], + want_scaled: [toY(876.57), toY(878.5)].map( + (y) => `translate(50 ${y}) scale(0.5) translate(-50 ${-y})`, + ), + }; +} + +// ── 1c. 평면 초안 상세 = 계획선 표본도 초안 종단(413 · 십자선 대체 높이) ───────────── +{ + const B05 = (name) => path.join(ROOT, "B05_Profile", "logic", `${name}.ts`); + const { draftSectionDetail } = makeLoader([ + B05("B05_Profile_Pipeline_Detail"), + B05("B05_Profile_UI_Profile_Data"), + ])(B05("B05_Profile_Pipeline_Detail")); + const draftSamples = [ + { chainage_m: 0, elevation_m: 110 }, + { chainage_m: 20, elevation_m: 112 }, + ]; + const saved = { + longitudinal: { + design_profiles: [ + { id: "old", name: "계획선", samples: [{ chainage_m: 0, elevation_m: 100 }] }, + ], + }, + cross_sections: [], + balloon_offsets: {}, + }; + const detail = draftSectionDetail( + saved, + { stations: [], longitudinal: { chainageM: [0, 20], z: [109, 111] } }, + { samples: draftSamples, balance: { cut_area_m2: 0, fill_area_m2: 0, net_area_m2: 0 } }, + null, + new Map(), + 20, + ); + const profile = detail.longitudinal.design_profiles[0]; + out.draftProfile = { samples: profile.samples, id: profile.id }; +} + // ── 2. 아래 띠(318-2 · 318-3) — 중심고 글 없음 · 암 경계 맨 왼쪽 · 토사도 띠 남음 ───── { const control = (cls) => () => { diff --git a/resources/tester/test_150_detail_pick.py b/resources/tester/test_150_detail_pick.py index 4cc40892b..24c19f86a 100644 --- a/resources/tester/test_150_detail_pick.py +++ b/resources/tester/test_150_detail_pick.py @@ -60,7 +60,10 @@ def test_칸마다_그림_강조(out): assert active["cut_rock"] == ["b06-chart__area--cut_rock"] assert active["embankment"] == ["b06-chart__area--fill"] assert "spoil_fill" not in out["targets"] # 사토장 횡단 그림 없음(380-1) - assert active["ground"] == ["b06-chart__center"] + # 413 — 원지반고 = 원지반 십자 · 계획고 = 계획 십자 · 절성토고 = 둘 다 + assert active["ground"] == ["b06-chart__ground-center"] + assert active["planned"] == ["b06-chart__center"] + assert active["cut"] == ["b06-chart__ground-center", "b06-chart__center"] assert active["ditch_soil"] == ["ditch"] assert active["grading_cut"] == ["cutSlope"] assert active["grading_fill"] == ["fillSlope"] diff --git a/resources/tester/test_164_detail_zoom.py b/resources/tester/test_164_detail_zoom.py index de19bf158..0785d055c 100644 --- a/resources/tester/test_164_detail_zoom.py +++ b/resources/tester/test_164_detail_zoom.py @@ -56,6 +56,8 @@ def test_축_고정_도형_층만_움직임_눈금_다시(out): assert out["restoredTicksShifted"] is True # 다시 떠도 이어 간 범위로 # 확대 — 십자는 중심(319,142 · 칸 가운데)에서 배율 되돌림 assert out["centerTransform"].startswith("translate(319 142) scale(0.869") + # 413 — 원지반 십자도 자기 중심(319,200)에서 배율 되돌림 + assert out["groundTransform"].startswith("translate(319 200) scale(0.869") assert out["noAxesHandle"] is True # 축 옵션 없음 = 줌만 assert out["noLayer"] is None # 도형 층 없음 = 줌 없음 @@ -105,7 +107,7 @@ def test_강조_붉은_한_벌_고름_hover_다름_빈칸(): for selector in ( ".b06-detail__cad .b06-chart__area.is-active > polygon {\n fill: color-mix(in srgb, var(--color-danger)", ".b06-detail__cad .b06-chart__design-hit.is-active {\n stroke: color-mix(in srgb, var(--color-danger)", - ".b06-detail__cad .b06-chart__center.is-active line {\n stroke: var(--color-danger);", + ".b06-detail__cad .b06-chart__ground-center.is-active line,\n.b06-detail__cad .b06-chart__center.is-active line {\n stroke: var(--color-danger);", ".b06-detail__qtable .is-picked {\n background: color-mix(in srgb, var(--color-danger) 18%", ".b06-detail__qtable [data-key][data-empty] {\n cursor: default;", ): diff --git a/resources/tester/test_318_cross_card.py b/resources/tester/test_318_cross_card.py index 0ef30c293..1523f27ca 100644 --- a/resources/tester/test_318_cross_card.py +++ b/resources/tester/test_318_cross_card.py @@ -135,15 +135,43 @@ def test_center_wired_into_card_and_modal() -> None: assert "centerZ: section.center_z ?? null" in _src( "B06_Section", "B06_Section_UI_Section_View.ts" ) - # 379-3 계획 상자 = 십자선 계획고(카드 designElevation · 모달 cardDesignElevation) - assert "designZ: hasDesign ? (designElevation as number) : null" in _src( - "B06_Section", "B06_Section_UI_Cross_View.ts" - ) - assert "designZ: cardDesignElevation(detail, section) ?? null" in _src( - "B06_Section", "B06_Section_UI_Section_View.ts" + # 379-3 계획 상자 = 십자선 계획고(카드 designZ · 모달 cardDesignElevation) + # 413 — 십자선 · 계획 상자 = 그 측점 설계 높이(형상과 한 값) · 설계 없을 때만 종단 표본 + card = _src("B06_Section", "B06_Section_UI_Cross_View.ts") + assert "const designZ = section.design?.design_elevation_m ?? designElevation;" in card + assert "designZ: hasDesign ? (designZ as number) : null" in card + assert "designElevation: hasDesign ? (designZ as number) : null" in card + assert "groundElevation: section.center_z ?? null" in card + view = _src("B06_Section", "B06_Section_UI_Section_View.ts") + assert "designZ: cardDesignElevation(detail, section) ?? null" in view + assert ( + "if (section.design?.design_elevation_m !== undefined) return section.design.design_elevation_m;" + in view ) +def test_two_crosshairs(result: dict) -> None: + """413 — 원지반 십자(원지반 색 · 다른 무리) 먼저 · 계획 십자 나중(위) · 높이 = 설계 · 원지반.""" + cross = result["crosshair"] + assert cross["order"] == ["b06-chart__ground-center", "b06-chart__center"] + assert abs(cross["design_y"] - cross["want_design_y"]) < 1e-6 # 설계 높이(종단 표본 아님) + assert abs(cross["ground_y"] - cross["want_ground_y"]) < 1e-6 # center_z + assert cross["design_only_ground"] is None # 설계 없음 = 십자 하나(지반 폴백) + assert cross["scaled"] == cross["want_scaled"] # 둘 다 자기 중심에서 배율 되돌림 + areas = _src("B06_Section", "B06_Section_UI_Style_Cross_Areas.css") + assert "var(--color-chart-1)" in _rule(areas, ".b06-chart__ground-center-marker") + + +def test_draft_detail_profile_is_draft(result: dict) -> None: + """413 — 평면 초안 상세의 계획선 표본 = 초안 종단(저장 종단 아님) · 이름은 저장분 그대로.""" + draft = result["draftProfile"] + assert draft["samples"] == [ + {"chainage_m": 0, "elevation_m": 110}, + {"chainage_m": 20, "elevation_m": 112}, + ] + assert draft["id"] == "old" + + # ── 318-2 · 318-3 양식 ──────────────────────────────────────────────────────