fix(B07): 횡단도 노란 측구 덧그림 걷음 · 배수관 · 박스암거 덧그림 색 따로(268)

- 측구 구간 구조물 선(b08-structure)도 design_ids 에 실음 — 화면 구조물 덧그림이 같은 자리를 그릴 때 서버 노란 측구가 한 겹 더 남던 것 걷음 (장 · 단일 도면)
- 박스암거 · 배수관 덧그림을 구조물(노랑)과 다른 청록(#22d3ee)으로 — 설계선 보라 · 지반선과 구분
- 시험 test_268_b07_cross_ditch_pipe_color.py

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SE9MxeXTu2uaDZ55BJowAY
This commit is contained in:
2026-10-11 16:47:57 +09:00
co-authored by Claude Sonnet 5.5
parent f453a9ce43
commit d496517ce4
4 changed files with 139 additions and 5 deletions
@@ -29,6 +29,8 @@ const SVG_NS = "http://www.w3.org/2000/svg";
/** 구조물 엔티티가 들어갈 레이어·색 — 서버 도면이 이미 선언해 둔 그 레이어다. */
const STRUCTURE_LAYER_ID = "b08-structure";
const STRUCTURE_COLOR = "#f6d55c";
/** 배수관 · 박스암거 색 — 구조물(노랑) · 설계선(보라) · 지반선(흰/검정)과 또렷이 다른 청록(268-2). */
const PIPE_COLOR = "#22d3ee";
/** 설계선 도면층·색 — 서버 도면(`B07_DesignDetail_Engine_Cad`)이 쓰는 그 값이다. */
export const DESIGN_LAYER_ID = "b08-design";
const DESIGN_COLOR = "#b794f6";
@@ -49,6 +51,7 @@ interface Style {
}
const STRUCTURE_STYLE: Style = { layerId: STRUCTURE_LAYER_ID, color: STRUCTURE_COLOR };
const PIPE_STYLE: Style = { layerId: STRUCTURE_LAYER_ID, color: PIPE_COLOR };
const DESIGN_STYLE: Style = { layerId: DESIGN_LAYER_ID, color: DESIGN_COLOR };
function baseEntity(style: Style, type: string, shapeData: unknown): Entity {
@@ -494,9 +497,10 @@ function drawDesign(
);
}
/** 구조물을 그린다. 그리는 순서는 B06 카드와 같다. */
/** 구조물을 그린다. 그리는 순서는 B06 카드와 같다 — 박스암거 · 배수관은 pipeSvg 로 따로(268-2). */
function drawStructures(
svg: SVGElement,
pipeSvg: SVGElement,
section: CrossSection,
layouts: Layouts,
x: (offset: number) => number,
@@ -504,12 +508,12 @@ function drawStructures(
): void {
const { link, culvert, box, ford, own } = layouts;
if (own) appendRevetmentOverlay(svg, own, x, y);
if (box) appendBoxOverlay(svg, box, x, y);
if (box) appendBoxOverlay(pipeSvg, box, x, y);
if (ford) appendFordOverlay(svg, ford, x, y);
if (culvert) {
const linked = !section.culvert && !!link;
appendCulvertOverlay(
svg,
pipeSvg,
culvert,
x,
y,
@@ -536,9 +540,11 @@ export function overlayEntities(
const designSvg = document.createElementNS(SVG_NS, "svg");
drawDesign(designSvg, section, layouts, x, y);
const structureSvg = document.createElementNS(SVG_NS, "svg");
drawStructures(structureSvg, section, layouts, x, y);
const pipeSvg = document.createElementNS(SVG_NS, "svg");
drawStructures(structureSvg, pipeSvg, section, layouts, x, y);
return [
...harvest(designSvg, DESIGN_STYLE, frame),
...harvest(structureSvg, STRUCTURE_STYLE, frame),
...harvest(pipeSvg, PIPE_STYLE, frame),
];
}
@@ -628,6 +628,12 @@ def build_cross_drawing(
# 블록 테두리(종이 mm). 프론트가 자기 그림을 이 안으로 자르고, 갈아 끼울
# 서버 설계선을 이 안에서만 골라내는 데 쓴다.
"frame": [frame_left, frame_bottom, frame_right, frame_top],
# 화면이 다시 그려 갈아 끼울 서버 선 — 설계선 + 측구 구간 구조물 선(268-1)
"design_ids": [
e["id"]
for e in entities
if e["layerId"] in (DESIGN_LAYER_ID, STRUCTURE_LAYER_ID)
],
}
],
"layers": [
@@ -240,7 +240,9 @@ def build_cross_sheet(sheet: dict[str, Any], sections: list[dict[str, Any]]) ->
layer_id = f"{STATION_LAYER_PREFIX}{section['chainage']}"
title = section.get("title") or f"{section['chainage']}"
station_layers.append(_layer(layer_id, f"횡단 {title}"))
design_ids = [e["id"] for e in placed["entities"] if e["layerId"] == DESIGN_LAYER_ID]
# 측구 구간 구조물 선(b08-structure)도 갈아 끼울 대상 — 화면 구조물 덧그림이 같은 자리를 그림(268-1)
replaceable = (DESIGN_LAYER_ID, STRUCTURE_LAYER_ID)
design_ids = [e["id"] for e in placed["entities"] if e["layerId"] in replaceable]
for entity in placed["entities"]:
_move_to_layer(entity, layer_id)
for placement in placed.get("cross_placements") or []:
@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
"""268 B07 횡단도 — 노란 측구 덧그림 · 배수관 · 박스암거 색.
못박는 것
- 268-1 측구 구간 구조물 선(b08-structure)도 design_ids 에 실려 화면 덧그림이 갈아 끼울 때 걷힘
(장 · 단일 도면 모두) · 설계선 · 측구 본 그림(구조물 선 자체)은 그대로 남음.
- 268-2 박스암거 · 배수관 덧그림은 구조물(노랑) · 설계선(보라) · 지반선과 다른 색 한 벌.
"""
import re
from pathlib import Path
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import (
DESIGN_COLOR,
DESIGN_LAYER_ID,
GROUND_COLOR,
ROCK_COLOR,
STRUCTURE_COLOR,
STRUCTURE_LAYER_ID,
build_cross_drawing,
)
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Sheet import build_cross_sheet, plan_cross_sheets
from B07_DesignDetail.B07_DesignDetail_Engine_Template import use_project_templates
ROOT = Path(__file__).resolve().parents[2]
_DESIGN = {
"ditch_enabled": True,
"ditch_side": "left",
"ditch": {"type": "standard", "top_width_m": 1.0},
"road_edges": {"left": {"offset_m": -1.0}},
}
_LINE = [
{"offset_m": x, "elevation_m": 100.0 + (0.0 if x >= -1.0 else -0.3)}
for x in (-3.0, -2.0, -1.0, 0.0, 3.0)
]
def _source(chainage: float) -> dict:
return {
"chainage_m": chainage,
"samples": [
{"valid": True, "offset_m": float(x), "elevation_m": 100.0 + 0.1 * x}
for x in range(-5, 6)
],
}
def _ditch_ids(entities: list[dict]) -> list[str]:
return [e["id"] for e in entities if e["lineColor"] == STRUCTURE_COLOR]
def test_측구_구조물_선_design_ids_장() -> None:
use_project_templates(None)
sheet = plan_cross_sheets([0.0, 20.0], {"default": [2, 1]})[0]
sections = [
{
"chainage": c,
"source": _source(c),
"design": _DESIGN,
"design_line": _LINE,
"quantity_table": {"ground": 1.0, "planned": 0.5},
"title": f"No.{int(c // 20)}",
}
for c in (0.0, 20.0)
]
drawing = build_cross_sheet(sheet, sections)
by_id = {e["id"]: e for e in drawing["entities"]}
ditch = _ditch_ids(drawing["entities"])
assert len(ditch) == 2 # 측점마다 측구 구간 선 하나
for placement in drawing["cross_placements"]:
ids = placement["design_ids"]
assert len(set(ids) & set(ditch)) == 1 # 자기 측점 측구 선만
assert all(by_id[i]["lineColor"] in (DESIGN_COLOR, STRUCTURE_COLOR) for i in ids)
# 걷기는 화면이 하고, 서버 도면은 측구 구조물 선을 그대로 가짐
assert all(by_id[i]["lineColor"] == STRUCTURE_COLOR for i in ditch)
def test_측구_구조물_선_design_ids_단일_도면() -> None:
use_project_templates(None)
drawing = build_cross_drawing(
_source(0.0), "d", _LINE, _DESIGN, {"ground": 1.0, "planned": 0.5}, "No.0"
)
ditch = [e for e in drawing["entities"] if e["layerId"] == STRUCTURE_LAYER_ID]
assert len(ditch) == 1
design_ids = drawing["cross_placements"][0]["design_ids"]
assert ditch[0]["id"] in design_ids
assert all(
e["layerId"] in (DESIGN_LAYER_ID, STRUCTURE_LAYER_ID)
for e in drawing["entities"]
if e["id"] in design_ids
)
def test_측구_없으면_design_ids_설계선만() -> None:
use_project_templates(None)
drawing = build_cross_drawing(
_source(0.0), "d", _LINE, None, {"ground": 1.0, "planned": 0.5}, "No.0"
)
ids = drawing["cross_placements"][0]["design_ids"]
layers = {e["layerId"] for e in drawing["entities"] if e["id"] in ids}
assert layers == {DESIGN_LAYER_ID}
def test_배관_박스암거_색_따로() -> None:
src = (ROOT / "B07_DesignDetail/B07_DesignDetail_Cross_Overlay.ts").read_text(encoding="utf-8")
pipe = re.search(r'const PIPE_COLOR = "(#[0-9a-fA-F]{6})"', src)
assert pipe, "PIPE_COLOR 없음"
color = pipe.group(1).lower()
assert color not in {STRUCTURE_COLOR, DESIGN_COLOR, GROUND_COLOR, ROCK_COLOR}
assert color not in {
"#8ea0b5",
"#e8edf4",
"#ffe066",
"#ff4d4d",
} # 표 선 · 글 · 값 · 구조물 평면 색
# 박스암거 · 배수관만 pipeSvg 로 — 구조물(돌붙임 · 여울)은 그대로 노랑
assert "appendBoxOverlay(pipeSvg," in src and "appendCulvertOverlay(\n pipeSvg," in src
assert "appendRevetmentOverlay(svg," in src and "appendFordOverlay(svg," in src
assert "harvest(pipeSvg, PIPE_STYLE, frame)" in src