From d77fd365941475a2808fb29466644c32f6a05bda Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 10 Oct 2026 13:16:27 +0900 Subject: [PATCH 1/3] =?UTF-8?q?chore(req):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EC=9A=94=EA=B5=AC=2038~42=20=EC=9B=90=EB=AC=B8=20=EB=B0=98?= =?UTF-8?q?=EC=98=81(190)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU --- docs/raw/요구_1010_2/사용자요구_1010_2.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/raw/요구_1010_2/사용자요구_1010_2.md b/docs/raw/요구_1010_2/사용자요구_1010_2.md index a0da62159..0454c3f5a 100644 --- a/docs/raw/요구_1010_2/사용자요구_1010_2.md +++ b/docs/raw/요구_1010_2/사용자요구_1010_2.md @@ -47,3 +47,19 @@ ## 추가 (182장 뒤 · 23번 보충) 31. 설계 내역서의 내용은 기본적으로 들어간 내용은 고정이 아님. 초기에 기본적으로 만드는 로직은 있지만 이걸 사용자가 수정할 수 있어야하며 추가하는 행과 삭제도 자유로워야함. 다시 말해 자유롭게 작성이 되고 단가 산출과 일위대가에 연결할 수 있어야한다는 것임. 32. 추가로 특정 셀의 텍스트 입력으로 마우스 우클릭이 브라우져 옵션이 보임. 우리프로그램에서는 우클릭으로 브라우져 옵션보이기는 없는 상태임.참고 +33. 총관설계 내역서에도 행들이 레벨 구분이 되어야함. +34. 박스보기가 구현되어 있는 버튼에 줄바꿈이 존재함. 1행으로 존재하게 변경하고 같이 존제하는 가로 개수 설정의 버튼들도 같은 행에 존재하게 해줘. +35. 자재단가대비표의에서 견적항목의 경우 단가를 수기입력하는 정보들만 견적서를 첨부하는 글자가 보이게 반영 +36. 자재단가대비표의 출처는 있어야하는 정보가 맞으나 현재 페이지에서는 중요한 정보가 아니니 출처정보는 마우스커서를 올린 셀의 정보를 보이게 하는데 숫자들의 의미를 모름 풀어서 짧게 정보를 보여줘. +37. 4번 요청에 이어 프로그램 전역에서 호버기능으로 추가정보를 보여줄때의 공용양식이 있었으면 함. +38. (7번 보충) 흙막이류 횡단도 형상 반영 문제_랩탑 메인 프로젝트 14+12측점임. 데이터 확인 +39. 종단그래프 상단 초기화버튼부터 쉬프트 상하버튼까지 우측 배수유역도와 새창 버튼과 사이즈를 유사하게 변경. 형태가 같을 필요는 없음. 필요한 경우 템플릿으로 만들어서 적용해도됨. +40. 종단페이지에서 배관지점을 하나찌고 다시 일반 측점을 찍은 상태에서 횡단으로 넘어가면 배관지점이 일반측점을 찍은 상태가 해제되고 직전 배관측점을 지정한곳으로 이동. 사용자가 최종선택한 정보를 가진 변수가 제대로 동작하는 확인 +41. 구조물중 여전히 노면공의 항목을 선택하는게 문제가 있음. + 1. 좌측 패널에서 구조물 리스트를 선택하는 경우 해당지점에 대한 전체측점 횡단도의 포현은 좋음 + 2. 이때 횡단도 내부에 구조물 형상도 하이라이트 되는건 좋음. + 3. 구조물 형상을 선택할때는 전체 측점들의 컨테이너와 구조물 형상 하이라이트 반영(현재 선택안됨) + 4. 이때 특정 하나의 횡단도를 선택한다면 구조물 리스트와 컨테이너 하이라이트를 끄고 선택된 횡단도를 기준으로 다시 표현되어야함. + 5. 여러 측점을 거치기때문에 하나의 측점 횡단도를 기준으로 삼기 어려움. 종단에서 표현 안되게 + 6. 단, 종단 세로선이나 횡단도를 선택할때는 해당 지점에 대한 하이라이트는 되어야함. +42. B06의 집수정 형상 선택에 대해 조금더 넓은 영역이 선택할 수 있게 변경(날개벽 선택 참고 가상의 선을 그려서 사각 형태의 가상의 영역을 선택영역으로 포함.) From fafa0cbe918c54d108b9f7343fb9b9b019b04112 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 10 Oct 2026 13:22:22 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat(b06):=20=EC=A7=91=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EA=B3=A0=EB=A5=B4=EA=B8=B0=20=ED=8C=90=EC=A0=95=EB=A9=B4=20=3D?= =?UTF-8?q?=20=EB=B6=80=EC=9E=AC=20=EC=99=B8=EA=B3=BD=20=EA=B0=80=EC=83=81?= =?UTF-8?q?=20=EC=82=AC=EA=B0=81=ED=98=95=20=C2=B7=20=EC=84=B8=EC=9B=94?= =?UTF-8?q?=EA=B5=90=20=EC=B8=A1=EB=B2=BD=EA=B3=BC=20=EA=B0=99=EC=9D=80=20?= =?UTF-8?q?outlineBox=20=ED=95=9C=20=EB=B2=8C(190-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU --- B06_Section/B06_Section_UI_Cross_Culvert.ts | 30 +-- .../B06_Section_UI_Cross_Culvert_Types.ts | 14 ++ B06_Section/B06_Section_UI_Cross_Ford.ts | 14 +- resources/tester/helper_190_1_basin_pick.cjs | 190 ++++++++++++++++++ resources/tester/test_190_1_basin_pick.py | 85 ++++++++ 5 files changed, 307 insertions(+), 26 deletions(-) create mode 100644 resources/tester/helper_190_1_basin_pick.cjs create mode 100644 resources/tester/test_190_1_basin_pick.py diff --git a/B06_Section/B06_Section_UI_Cross_Culvert.ts b/B06_Section/B06_Section_UI_Cross_Culvert.ts index 6f74b9983..640890541 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert.ts @@ -21,6 +21,7 @@ import type { PipeEnd, WallLayout, } from "./B06_Section_UI_Cross_Culvert_Geom"; +import { outlineBox } from "./B06_Section_UI_Cross_Culvert_Types"; import { appendWallTrench, foundationChoice } from "./B06_Section_UI_Cross_Excavation"; import { appendChuteBand, @@ -371,20 +372,21 @@ export function appendCulvertOverlay( layer.append(hit); } // 집수정도 선택 대상(2026-08-22 사용자 — 형식 드롭다운). 자리 이동은 없고 - // 조정창에서 구조물 형식만 고른다. - if (layout.basin) { - for (const part of layout.basin.parts) { - const hit = polygon( - part.points.map((p) => [x(p.offset), toDisplayY(p.elevation)] as [number, number]), - "b06-chart__culvert-revet-hit", - "유입 집수정 선택 — 조정창에서 구조물 형식 선택", - ); - hit.addEventListener("click", (event) => { - event.stopPropagation(); - onSelectRevet("inlet"); - }); - layer.append(hit); - } + // 조정창에서 구조물 형식만 고른다. 판정면 = 부재 전체를 감싸는 가상 사각형(190-1 · 세월교 측벽 꼴). + const basinPoints = layout.basin?.parts.flatMap((part) => part.points) ?? []; + if (basinPoints.length) { + const hit = polygon( + outlineBox(basinPoints).map( + (p) => [x(p.offset), toDisplayY(p.elevation)] as [number, number], + ), + "b06-chart__culvert-revet-hit", + "유입 집수정 선택 — 조정창에서 구조물 형식 선택", + ); + hit.addEventListener("click", (event) => { + event.stopPropagation(); + onSelectRevet("inlet"); + }); + layer.append(hit); } } diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts index 56c2499c6..94dc53ea0 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts @@ -36,6 +36,20 @@ export interface OffsetPoint { elevation: number; } +/** 점들을 감싸는 가상 사각형 — 고르기 판정면만(그림 아님) · 세월교 측벽 ㄴ(79-6) · 집수정(190-1). */ +export function outlineBox(points: OffsetPoint[]): OffsetPoint[] { + const offsets = points.map((point) => point.offset); + const elevations = points.map((point) => point.elevation); + const [left, right] = [Math.max(...offsets), Math.min(...offsets)]; + const [top, bottom] = [Math.max(...elevations), Math.min(...elevations)]; + return [ + { offset: left, elevation: top }, + { offset: right, elevation: top }, + { offset: right, elevation: bottom }, + { offset: left, elevation: bottom }, + ]; +} + /** 설계선(성토 경사) 트림 범위 — 이 밖은 벽·보호공이 대신하므로 그리지 않는다. */ export interface CulvertDesignTrim { minOffset: number; diff --git a/B06_Section/B06_Section_UI_Cross_Ford.ts b/B06_Section/B06_Section_UI_Cross_Ford.ts index f5577ca81..769e7ce9f 100644 --- a/B06_Section/B06_Section_UI_Cross_Ford.ts +++ b/B06_Section/B06_Section_UI_Cross_Ford.ts @@ -10,7 +10,7 @@ * ========================================================================== */ import type { FordLayout, FordWallRole } from "./B06_Section_UI_Cross_Ford_Geom"; -import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types"; +import { outlineBox, type OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types"; import { appendChuteBand } from "./B06_Section_UI_Cross_Wall_Hatch"; export { @@ -187,17 +187,7 @@ export function appendFordOverlay( for (const side of layout.sides) { const points = side.parts.flatMap((part) => part.points); if (!points.length) continue; - const offsets = points.map((point) => point.offset); - const elevations = points.map((point) => point.elevation); - const [left, right] = [Math.max(...offsets), Math.min(...offsets)]; - const [top, bottom] = [Math.max(...elevations), Math.min(...elevations)]; - const box = [ - { offset: left, elevation: top }, - { offset: right, elevation: top }, - { offset: right, elevation: bottom }, - { offset: left, elevation: bottom }, - ]; - const hit = polygon(box, "b06-chart__culvert-revet-hit", ""); + const hit = polygon(outlineBox(points), "b06-chart__culvert-revet-hit", ""); hit.addEventListener("click", (event) => { event.stopPropagation(); onSelectWall(side.role); diff --git a/resources/tester/helper_190_1_basin_pick.cjs b/resources/tester/helper_190_1_basin_pick.cjs new file mode 100644 index 000000000..a09a1949b --- /dev/null +++ b/resources/tester/helper_190_1_basin_pick.cjs @@ -0,0 +1,190 @@ +/* PLAN 190-1 단위검증 헬퍼 — B06 집수정 판정면 = 부재 외곽 가상 사각형(세월교 측벽 꼴). + * 화면 TS 를 그 자리에서 트랜스파일해 Node 로 돌림 · 결과 = 표준출력 JSON 한 벌. + * 입력 = `fixtures/b07_cross_overlay_sections.json` 배관 측점 + design.inlet_structure 강제(U · L · I). + * 짝 = `test_190_1_basin_pick.py`. + */ +const fs = require("fs"); +const path = require("path"); +const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); + +const ROOT = path.join(__dirname, "..", ".."); +const cache = new Map(); +const STUBS = { + "@ui/ui_template_toast": { showToast: () => undefined }, + "@ui/ui_template_elements": { showToast: () => undefined }, +}; + +// ── SVG 그릇 흉내 — 오버레이가 쓰는 만큼만 ───────────────────────────────── +class FakeNode { + constructor(tag) { + this.tagName = String(tag).toUpperCase(); + this.children = []; + this.attrs = {}; + this.listeners = {}; + this.textContent = ""; + this._classes = new Set(); + const self = this; + this.classList = { + add: (...names) => names.forEach((n) => self._classes.add(n)), + toggle: (name, force) => { + const on = force === undefined ? !self._classes.has(name) : !!force; + on ? self._classes.add(name) : self._classes.delete(name); + return on; + }, + contains: (name) => self._classes.has(name), + }; + } + append(...kids) { + kids.forEach((k) => this.children.push(k)); + } + appendChild(kid) { + this.children.push(kid); + return kid; + } + setAttribute(name, value) { + this.attrs[name] = String(value); + if (name === "class") this._classes = new Set(String(value).split(/\s+/).filter(Boolean)); + } + getAttribute(name) { + return this.attrs[name] ?? null; + } + addEventListener(type, fn) { + (this.listeners[type] ??= []).push(fn); + } + click() { + let stopped = false; + const event = { stopPropagation: () => (stopped = true), preventDefault: () => {} }; + (this.listeners.click ?? []).forEach((fn) => fn(event)); + return stopped; + } +} +globalThis.document = { + createElement: (tag) => new FakeNode(tag), + createElementNS: (_ns, tag) => new FakeNode(tag), +}; + +function loadTs(file) { + const full = path.resolve(file); + if (cache.has(full)) return cache.get(full); + if (full.endsWith(".json")) { + const data = JSON.parse(fs.readFileSync(full, "utf8")); + cache.set(full, data); + return data; + } + const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2020, + esModuleInterop: true, + }, + }).outputText; + const box = { exports: {} }; + cache.set(full, box.exports); + const localRequire = (request) => { + if (STUBS[request]) return STUBS[request]; + const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; + for (const [prefix, dir] of Object.entries(alias)) + if (request.startsWith(prefix)) + return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); + return loadTs(resolve(path.join(path.dirname(full), request))); + }; + new Function("exports", "module", "require", js)(box.exports, box, localRequire); + cache.set(full, box.exports); + return box.exports; +} +const resolve = (base) => + [base, `${base}.ts`, path.join(base, "index.ts")].find( + (p) => fs.existsSync(p) && fs.statSync(p).isFile(), + ) ?? `${base}.ts`; + +const B06 = (f) => path.join(ROOT, "B06_Section", `${f}.ts`); +const { computeStoredLayouts } = loadTs(B06("B06_Section_Structure_Layouts")); +const { appendCulvertOverlay } = loadTs(B06("B06_Section_UI_Cross_Culvert")); +const { outlineBox } = loadTs(B06("B06_Section_UI_Cross_Culvert_Types")); + +const fixture = JSON.parse( + fs.readFileSync(path.join(__dirname, "fixtures", "b07_cross_overlay_sections.json"), "utf8"), +); +const sections = fixture.cross_sections; +const pipeSection = sections.find((s) => s.culvert && !s.culvert.hidden_pipe); +const BASIN_TIP = "유입 집수정 선택 — 조정창에서 구조물 형식 선택"; +const X = (o) => -o * 10; +const Y = (e) => -e * 10; +const pointsOf = (node) => node.attrs.points.split(" ").map((pair) => pair.split(",").map(Number)); + +function basinCase(shape) { + const target = { ...pipeSection, design: { ...pipeSection.design, inlet_structure: shape } }; + const layout = computeStoredLayouts(target, sections).culvert; + const layer = new FakeNode("g"); + const picked = []; + const setter = appendCulvertOverlay(layer, layout, X, Y, (key) => picked.push(key)); + const hits = layer.children.filter((n) => n.classList.contains("b06-chart__culvert-revet-hit")); + const basinHits = hits.filter((n) => n.children[0]?.textContent === BASIN_TIP); + const all = layout.basin.parts.flatMap((part) => part.points); + // 기대 외곽(화면 좌표) — 부재 전 꼭짓점의 최소 · 최대 + const xs = all.map((p) => X(p.offset)); + const ys = all.map((p) => Y(p.elevation)); + const expect = { + minX: Math.min(...xs), + maxX: Math.max(...xs), + minY: Math.min(...ys), + maxY: Math.max(...ys), + }; + const got = basinHits.length ? pointsOf(basinHits[0]) : []; + const gotBox = got.length + ? { + minX: Math.min(...got.map(([px]) => px)), + maxX: Math.max(...got.map(([px]) => px)), + minY: Math.min(...got.map(([, py]) => py)), + maxY: Math.max(...got.map(([, py]) => py)), + } + : null; + const stopped = basinHits.length ? basinHits[0].click() : false; + // 그림 모양은 그대로 — 집수정 부재 폴리곤 = 부재 점열 그대로 · 고르면 부재 강조 + const drawn = layer.children.filter((n) => n.classList.contains("b06-chart__culvert-basin")); + const drawnSame = drawn.every( + (n, i) => + n.attrs.points === + layout.basin.parts[i].points.map((p) => `${X(p.offset)},${Y(p.elevation)}`).join(" "), + ); + setter("inlet"); + const activeOnInlet = drawn.every((n) => n.classList.contains("is-active")); + return { + shape: layout.basin.shape, + parts: layout.basin.parts.length, + basinHits: basinHits.length, + corners: got.length, + expect, + got: gotBox, + picked, + stopped, + lastHitIsBasin: hits[hits.length - 1] === basinHits[0], + drawnParts: drawn.length, + drawnSame, + activeOnInlet, + }; +} + +const out = { U: basinCase("U"), L: basinCase("L"), I: basinCase("I") }; +// 세월교 측벽과 같은 함수 — ㄴ 점열의 외곽 +const ell = [ + { offset: 0, elevation: 0 }, + { offset: 0.3, elevation: 0 }, + { offset: 0.3, elevation: 2 }, + { offset: 1.5, elevation: 2 }, + { offset: 1.5, elevation: 2.3 }, + { offset: 0, elevation: 2.3 }, +]; +out.outline = outlineBox(ell); +// 판정면 없으면(onSelect 없음 · B07) 집수정 판정면도 없음 +{ + const target = { ...pipeSection, design: { ...pipeSection.design, inlet_structure: "U" } }; + const layout = computeStoredLayouts(target, sections).culvert; + const layer = new FakeNode("g"); + appendCulvertOverlay(layer, layout, X, Y); + out.noSelectHits = layer.children.filter((n) => + n.classList.contains("b06-chart__culvert-revet-hit"), + ).length; +} + +process.stdout.write(JSON.stringify(out)); diff --git a/resources/tester/test_190_1_basin_pick.py b/resources/tester/test_190_1_basin_pick.py new file mode 100644 index 000000000..cea785514 --- /dev/null +++ b/resources/tester/test_190_1_basin_pick.py @@ -0,0 +1,85 @@ +"""PLAN 190-1 — B06 집수정 고르기 판정면 = 부재 외곽 가상 사각형(세월교 측벽 ㄴ 꼴 · 79-6). + +짝: `B06_Section/B06_Section_UI_Cross_Culvert.ts`(집수정 판정면) · `_Cross_Ford.ts`(측벽 판정면) · +`_Cross_Culvert_Types.ts`(`outlineBox` 한 벌). [상세] 모달은 카드 그림 복제라 같은 판정면을 가짐. +실제 TS 를 `helper_190_1_basin_pick.cjs` 가 Node 로 돌린다. +""" + +from __future__ import annotations + +import json +import os +import subprocess + +import pytest + +HERE = os.path.dirname(__file__) +ROOT = os.path.join(HERE, "..", "..") + +pytestmark = pytest.mark.skipif( + not os.path.isdir(os.path.join(ROOT, "config", "node_modules")), + reason="프론트엔드 의존성 미설치", +) + + +@pytest.fixture(scope="module") +def out() -> dict: + proc = subprocess.run( # noqa: S603 — 고정 실행 파일 + ["node", os.path.join(HERE, "helper_190_1_basin_pick.cjs")], + capture_output=True, + text=True, + encoding="utf-8", + timeout=60, + ) + assert proc.returncode == 0, proc.stderr + return json.loads(proc.stdout) + + +def _src(rel: str) -> str: + with open(os.path.join(ROOT, rel), encoding="utf-8") as f: + return f.read() + + +@pytest.mark.parametrize("shape,parts", [("U", 3), ("L", 2), ("I", 1)]) +def test_집수정_판정면은_외곽_사각_하나(out, shape, parts) -> None: + case = out[shape] + assert case["shape"] == shape and case["parts"] == parts + assert case["basinHits"] == 1 # 부재마다가 아니라 한 장 + assert case["corners"] == 4 + for key in ("minX", "maxX", "minY", "maxY"): + assert case["got"][key] == pytest.approx(case["expect"][key], abs=1e-9) + + +@pytest.mark.parametrize("shape", ["U", "L", "I"]) +def test_누르면_집수정_고름(out, shape) -> None: + case = out[shape] + assert case["picked"] == ["inlet"] + assert case["stopped"] is True # 카드 고름으로 안 번짐 + assert case["lastHitIsBasin"] is True # 맨 위 — 관 판정면보다 위 + + +@pytest.mark.parametrize("shape", ["U", "L", "I"]) +def test_그림_모양은_그대로(out, shape) -> None: + case = out[shape] + assert case["drawnParts"] == case["parts"] + assert case["drawnSame"] is True + assert case["activeOnInlet"] is True + + +def test_외곽_사각_함수(out) -> None: + box = [(p["offset"], p["elevation"]) for p in out["outline"]] + assert box == [(1.5, 2.3), (0, 2.3), (0, 0), (1.5, 0)] + + +def test_고름_없으면_판정면_없음(out) -> None: + assert out["noSelectHits"] == 0 # B07 등 onSelect 없는 그림 + + +def test_세월교_측벽과_같은_함수() -> None: + types = _src("B06_Section/B06_Section_UI_Cross_Culvert_Types.ts") + assert "export function outlineBox(points: OffsetPoint[]): OffsetPoint[] {" in types + ford = _src("B06_Section/B06_Section_UI_Cross_Ford.ts") + assert 'polygon(outlineBox(points), "b06-chart__culvert-revet-hit", "")' in ford + assert "Math.max(...elevations)" not in ford # 측벽 외곽 따로 셈 없음 + culvert = _src("B06_Section/B06_Section_UI_Cross_Culvert.ts") + assert "outlineBox(basinPoints)" in culvert From 4805e0b21b9d78aebb97cf1ff5e863d3893d652a Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 10 Oct 2026 13:22:22 +0900 Subject: [PATCH 3/3] =?UTF-8?q?chore(req):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EC=9A=94=EA=B5=AC=2043=20=EC=9B=90=EB=AC=B8=20=EB=B0=98?= =?UTF-8?q?=EC=98=81(190)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU --- docs/raw/요구_1010_2/사용자요구_1010_2.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/raw/요구_1010_2/사용자요구_1010_2.md b/docs/raw/요구_1010_2/사용자요구_1010_2.md index 0454c3f5a..4cdf59a87 100644 --- a/docs/raw/요구_1010_2/사용자요구_1010_2.md +++ b/docs/raw/요구_1010_2/사용자요구_1010_2.md @@ -63,3 +63,4 @@ 5. 여러 측점을 거치기때문에 하나의 측점 횡단도를 기준으로 삼기 어려움. 종단에서 표현 안되게 6. 단, 종단 세로선이나 횡단도를 선택할때는 해당 지점에 대한 하이라이트는 되어야함. 42. B06의 집수정 형상 선택에 대해 조금더 넓은 영역이 선택할 수 있게 변경(날개벽 선택 참고 가상의 선을 그려서 사각 형태의 가상의 영역을 선택영역으로 포함.) +43. B06 횡단도 좌측 패널에서 세부 컨테이너의 정보를 변경하면 해당 구조물 형상을 잘 선택되고 있는데 해당 패널의 컨테이너 영역을 선택해도 변경되게 해줘.