fix(B06 십자선): 십자선 = 형상과 한 높이(측점 설계 높이) · 원지반 십자선 · 평면 초안 계획선 표본도 초안 종단 (413-1)

- 카드 · B05 오른쪽 판 · [상세] 창 십자선 · 계획 상자 = section.design.design_elevation_m · 설계 없을 때만 종단 표본
- 원지반 십자선(center_z · 원지반 색 · 다른 무리) 먼저 그림 · 겹치면 보라 위 · 확대 보정 둘 다
- [상세] 표 원지반고 = 원지반 십자 · 절성토고 = 둘 다
- 시험 318 · 150 · 164 고쳐 씀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
This commit is contained in:
2026-10-18 12:19:39 +09:00
co-authored by Claude Opus 5.5
parent 19ab7d54b9
commit c9b4f0bede
14 changed files with 210 additions and 54 deletions
+9 -1
View File
@@ -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",
+7 -3
View File
@@ -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;
@@ -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) => () => {
+4 -1
View File
@@ -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"]
+3 -1
View File
@@ -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;",
):
+34 -6
View File
@@ -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 양식 ──────────────────────────────────────────────────────