feat(382-2): B05 평면 공용 고름 연동(일반측점 = 횡단 판 · 법선 · 구조물 = 마름모 · 눈금 클릭 = 고름 적음) · 법선 = 측점 열쇠 · 발자국 비탈 끝 좌우 · 절성토 색
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
This commit is contained in:
@@ -12,12 +12,27 @@
|
||||
* 값 = 계산 묶음 그 측점(365-3) — 측점 · 지반 표본 = ground 단계 · 계획고 · 설계선 = 종단 → cross 단계
|
||||
* (B06 카드 · 새 노선 상세와 같은 함수 `stationSection`) · 노선 · 계획고가 바뀔 때마다(cross 차례) 다시 그림 ·
|
||||
* 서버 왕복 없음. 성토사면 길이는 B06 화면이 쓰는 `fillSlopeLengths`.
|
||||
*
|
||||
* 고름 연동(382-2) — 공용 고름 한 칸(`design_selection`) 구독: 일반측점 고름 = 그 측점 횡단 · 평면 법선 ·
|
||||
* 구조물 고름 = 평면 그 자리 마름모만 · 평면 눈금 클릭 = 공용 고름에 적음(`from` b05-plan · 제 알림 거름).
|
||||
* 보는 측점 = 측점 열쇠(grid:k) — 묶음 차례마다 열쇠로 다시 짚음(노선을 고쳐도 같은 측점).
|
||||
* 파란 법선 폭 = 그 측점 발자국 기록(가게 `footprint.records`) 비탈 끝 좌 · 우 · 깎기 여부(색).
|
||||
* ========================================================================== */
|
||||
|
||||
import { attachSvgZoomPan, type ZoomPanState } from "../A00_Common/b_svg_zoom_pan";
|
||||
import type { CrossPreviewResponse } from "./B05_Profile_Api_Replan";
|
||||
import { pagePipeline } from "../A00_Common/design/design_pipeline_page";
|
||||
import type { DesignPipeline } from "../A00_Common/design/logic/design_pipeline";
|
||||
import type {
|
||||
PipelineStation,
|
||||
PlanFootprint,
|
||||
} from "../A00_Common/design/logic/design_pipeline_types";
|
||||
import {
|
||||
nearestWithin,
|
||||
selectDesign,
|
||||
subscribeSelection,
|
||||
type DesignSelection,
|
||||
} from "../A00_Common/design/design_selection";
|
||||
import { useDesignStore } from "../A00_Common/design/design_store";
|
||||
import { stationSection } from "./logic/B05_Profile_Pipeline_Detail";
|
||||
import {
|
||||
@@ -27,6 +42,8 @@ import {
|
||||
} from "./B05_Profile_UI_RouteEdit_Cross_Draw";
|
||||
import { readByKey, stateKey } from "../A00_Common/b_page_state";
|
||||
import { stationLabel as formatStation } from "../common_util/common_util_station";
|
||||
import { sidePanelsOf } from "./B05_Profile_UI_RouteEdit_Chrome";
|
||||
import type { FootprintRecord } from "./logic/B05_Profile_PlanFootprint";
|
||||
|
||||
export interface CrossPreviewParams {
|
||||
projectId: string;
|
||||
@@ -43,19 +60,31 @@ export interface CrossPreviewParams {
|
||||
onShown?: () => void;
|
||||
}
|
||||
|
||||
/** 위 판에 떠 있는 측점과 그 좌·우 횡단 반폭(m) — 평면도가 반폭 선을 그리는 재료. */
|
||||
/** 고른 측점과 그 좌·우 폭(m · 좌 + 방향 거리) — 평면도가 법선 · 마름모를 그리는 재료. */
|
||||
export interface CrossWatch {
|
||||
chainageM: number;
|
||||
leftM: number;
|
||||
rightM: number;
|
||||
/** 비탈 끝 깎기(절토) 여부 — 발자국 기록이 없으면 없음(옛 파랑 · 반폭). */
|
||||
leftCut?: boolean;
|
||||
rightCut?: boolean;
|
||||
/** 구조물 · 관 자리 — 법선 없이 마름모만. */
|
||||
structure?: true;
|
||||
}
|
||||
|
||||
/** 공용 고름에 적는 이름 — 평면 눈금 클릭(제 알림은 거름). */
|
||||
export const PLAN_PICK_FROM = "b05-plan";
|
||||
|
||||
/** 법선을 긋는 측점 갈래 — 일반측점(시점 · 종점 포함). */
|
||||
const isGridStation = (station: PipelineStation): boolean =>
|
||||
station.kind === "grid" || station.kind === "end";
|
||||
|
||||
export interface CrossPreviewWindow {
|
||||
/** 그 측점의 횡단을 위 판에 낸다. 아래 판은 비운다 — 전후 비교는 노선을 고쳤을 때만. */
|
||||
open: (chainageM: number) => Promise<void>;
|
||||
/** 다시 그림(묶음 지금 값) — 노선이 바뀌었으면 전후로. 보던 것이 없으면 아무 일도 없다. */
|
||||
refresh: () => Promise<void>;
|
||||
/** 위 판에 떠 있는 측점과 반폭. 떠 있지 않으면 null. */
|
||||
/** 고른 측점과 폭(발자국 비탈 끝 · 없으면 반폭) · 구조물이면 마름모. 고른 것 없으면 null. */
|
||||
watched: () => CrossWatch | null;
|
||||
}
|
||||
|
||||
@@ -193,18 +222,33 @@ export function createCrossPreview(params: CrossPreviewParams): CrossPreviewWind
|
||||
params.side.append(current.root, previous.root);
|
||||
const interval = (): number => params.request().station_interval_m;
|
||||
|
||||
/** 지금 보고 있는 측점(누가거리). 아직 없으면 null. */
|
||||
let watching: number | null = null;
|
||||
/** 지금 보는 측점 — 열쇠(묶음 전 null) · 누가거리(열쇠를 못 찾을 때) · 구조물 고름(마름모만). */
|
||||
let watching: { key: string | null; chainageM: number; structure: boolean } | null = null;
|
||||
/** 위 판에 그려 둔 것 — 다음 번에 아래로 내릴 재료 · 그때 노선 초안(바뀌면 전후로). */
|
||||
let shown: CrossPreviewResponse | null = null;
|
||||
let drawnRoute: unknown = null;
|
||||
|
||||
const stationsNow = (): PipelineStation[] => pipe?.output("ground")?.stations ?? [];
|
||||
/** 열쇠로 다시 짚은 지금 측점 — 열쇠가 없으면 들고 있던 누가거리 근처. */
|
||||
function located(): PipelineStation | null {
|
||||
if (!watching) return null;
|
||||
const list = stationsNow();
|
||||
const key = watching.key;
|
||||
const at = watching.chainageM;
|
||||
return (
|
||||
(key ? list.find((station) => station.key === key) : undefined) ??
|
||||
nearestWithin(list, at, (station) => station.chainageM) ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
function redraw(): void {
|
||||
if (watching === null) return;
|
||||
if (watching === null || watching.structure) return;
|
||||
const route = pipe?.input("route") ?? null;
|
||||
if (route !== drawnRoute && shown) previous.show(shown, interval());
|
||||
drawnRoute = route;
|
||||
shown = pipelineCrossAt(pipe, watching, interval(), appliedHalfWidth(params.projectId));
|
||||
const at = located()?.chainageM ?? watching.chainageM;
|
||||
shown = pipelineCrossAt(pipe, at, interval(), appliedHalfWidth(params.projectId));
|
||||
if (shown) current.show(shown, interval());
|
||||
else current.wait("계산 중…");
|
||||
params.onShown?.();
|
||||
@@ -230,33 +274,98 @@ export function createCrossPreview(params: CrossPreviewParams): CrossPreviewWind
|
||||
const stop = (): void => {
|
||||
stopStore();
|
||||
stopPipe();
|
||||
stopPick();
|
||||
};
|
||||
|
||||
/** 그 측점 횡단을 위 판에 · 아래 판은 비움(전후 비교는 노선을 고쳤을 때만) · 접힌 판은 폄. */
|
||||
function showStation(key: string | null, chainageM: number): void {
|
||||
// 아래 판은 **노선을 고쳤을 때만** 채운다(2026-09-12 사용자 지시 ⑤). 예전에는 같은
|
||||
// 측점을 두 번 누르면 위·아래에 **같은 그림 두 장**이 떠 전후 비교가 되지 않았다.
|
||||
previous.show(null, interval());
|
||||
watching = { key, chainageM, structure: false };
|
||||
drawnRoute = pipe?.input("route") ?? null;
|
||||
shown = null;
|
||||
redraw();
|
||||
const panels = sidePanelsOf.get(params.side);
|
||||
if (panels && panels.current() === null) panels.open("cross");
|
||||
}
|
||||
|
||||
/** 다른 보기(종단 · 3D · 목록 · 횡단 카드) 고름 → 일반측점이면 그 측점 횡단 · 구조물이면 마름모. */
|
||||
function follow(selection: DesignSelection): void {
|
||||
attach();
|
||||
const at = selection.chainageM;
|
||||
if (at === null) {
|
||||
watching = null;
|
||||
shown = null;
|
||||
current.show(null, interval());
|
||||
previous.show(null, interval());
|
||||
params.onShown?.();
|
||||
return;
|
||||
}
|
||||
const station = nearestWithin(stationsNow(), at, (entry) => entry.chainageM);
|
||||
if (!selection.structureId && (!station || isGridStation(station)))
|
||||
return showStation(station?.key ?? null, station?.chainageM ?? at);
|
||||
watching = { key: station?.key ?? null, chainageM: station?.chainageM ?? at, structure: true };
|
||||
params.onShown?.();
|
||||
}
|
||||
const stopPick = subscribeSelection(params.projectId, (selection, from) => {
|
||||
if (from !== PLAN_PICK_FROM) follow(selection);
|
||||
});
|
||||
attach();
|
||||
|
||||
/** 그 측점 발자국 기록(열쇠 · 없으면 누가거리) — 발자국 단계 전이면 없음. */
|
||||
const footprintAt = (key: string | null, chainageM: number): FootprintRecord | undefined => {
|
||||
const footprint = useDesignStore(params.projectId, null).get("footprint") as
|
||||
PlanFootprint | null | undefined;
|
||||
const records = (footprint?.records ?? []) as FootprintRecord[];
|
||||
return (
|
||||
(key ? records.find((record) => record.key === key) : undefined) ??
|
||||
nearestWithin(records, chainageM, (record) => record.chainage_m)
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
async open(chainageM) {
|
||||
// 아래 판은 **노선을 고쳤을 때만** 채운다(2026-09-12 사용자 지시 ⑤). 예전에는 같은
|
||||
// 측점을 두 번 누르면 위·아래에 **같은 그림 두 장**이 떠 전후 비교가 되지 않았다.
|
||||
previous.show(null, interval());
|
||||
watching = chainageM;
|
||||
attach();
|
||||
drawnRoute = pipe?.input("route") ?? null;
|
||||
shown = null;
|
||||
redraw();
|
||||
const list = stationsNow();
|
||||
const station = list.length
|
||||
? list.reduce((best, next) =>
|
||||
Math.abs(next.chainageM - chainageM) < Math.abs(best.chainageM - chainageM)
|
||||
? next
|
||||
: best,
|
||||
)
|
||||
: null;
|
||||
const at = station?.chainageM ?? chainageM;
|
||||
showStation(station?.key ?? null, at);
|
||||
selectDesign(
|
||||
params.projectId,
|
||||
{ chainageM: at, structureId: null },
|
||||
{ from: PLAN_PICK_FROM },
|
||||
);
|
||||
},
|
||||
async refresh() {
|
||||
attach();
|
||||
redraw();
|
||||
},
|
||||
// 자리는 보던 측점 그대로 — 노선을 끄는 동안 평면도가 그 누가거리로 다시 짚는다.
|
||||
// 반폭 = 적용해 둔 「횡단 반폭」 · 없으면 그린 원지반 선 범위 — 좌·우 같음.
|
||||
watched: () =>
|
||||
watching === null || !shown
|
||||
? null
|
||||
: {
|
||||
chainageM: watching,
|
||||
leftM: shown.cross_half_width_m,
|
||||
rightM: shown.cross_half_width_m,
|
||||
},
|
||||
// 자리 = 열쇠로 다시 짚은 측점 — 노선을 끄는 동안 평면도가 그 누가거리로 다시 짚는다.
|
||||
// 폭 = 그 측점 발자국 비탈 끝 좌 · 우 · 기록이 없으면 「횡단 반폭」(없으면 원지반 선 범위) 양쪽.
|
||||
watched: () => {
|
||||
if (!watching) return null;
|
||||
const station = located();
|
||||
const chainageM = station?.chainageM ?? watching.chainageM;
|
||||
if (watching.structure || (station && !isGridStation(station)))
|
||||
return { chainageM, leftM: 0, rightM: 0, structure: true };
|
||||
const record = footprintAt(station?.key ?? watching.key, chainageM);
|
||||
if (record)
|
||||
return {
|
||||
chainageM,
|
||||
leftM: record.left_end_m,
|
||||
rightM: -record.right_end_m,
|
||||
leftCut: record.left_cut,
|
||||
rightCut: record.right_cut,
|
||||
};
|
||||
if (!shown) return null;
|
||||
return { chainageM, leftM: shown.cross_half_width_m, rightM: shown.cross_half_width_m };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -390,8 +390,9 @@ function drawMeasureMarks(
|
||||
}
|
||||
|
||||
/**
|
||||
* 횡단이 떠 있는 측점의 **좌·우 반폭 선** — 측점에서 노선에 직각, 길이 = 우측 패널 「횡단 반폭」
|
||||
* (평면 거리 · 횡단 미리보기의 원지반 선 범위와 같다).
|
||||
* 고른 측점의 **좌·우 법선**(382-2) — 측점에서 노선에 직각, 길이 = 그 측점 발자국 비탈 끝 좌 · 우 ·
|
||||
* 색 = 깎기(절토) · 쌓기(성토) 토큰(`_Footprint` 와 같은 것) · 기록이 없으면 「횡단 반폭」 양쪽 파랑 ·
|
||||
* 측점 자리에 고리(눈금 강조). 구조물 · 관 고름 = 그 자리 마름모(배수유역도 `markStation` 꼴)만.
|
||||
*
|
||||
* 자리·방향은 **지금 그려진 노선**으로 매 프레임 다시 짚는다(`stationFrameAt` — 서버
|
||||
* `generate_sections` 짝) — 노드를 끄는 동안 선이 네트워크 없이 바로 따라간다.
|
||||
@@ -407,17 +408,37 @@ function drawCrossWidth(
|
||||
if (!frame) return;
|
||||
const [ox, oy] = frame.origin;
|
||||
const [lx, ly] = frame.left;
|
||||
const ends: Vertex[] = [
|
||||
[ox + lx * watch.leftM, oy + ly * watch.leftM],
|
||||
[ox - lx * watch.rightM, oy - ly * watch.rightM],
|
||||
];
|
||||
const [cx, cy] = scene.toScreen(frame.origin);
|
||||
context.save();
|
||||
context.strokeStyle = CROSS_WIDTH_COLOR;
|
||||
context.fillStyle = CROSS_WIDTH_COLOR;
|
||||
context.setLineDash([]);
|
||||
if (watch.structure) {
|
||||
context.translate(cx, cy);
|
||||
context.rotate(Math.PI / 4);
|
||||
context.beginPath();
|
||||
context.rect(-7, -7, 14, 14);
|
||||
context.fillStyle = "rgba(255, 196, 0, 0.35)";
|
||||
context.fill();
|
||||
context.lineWidth = 2.5;
|
||||
context.strokeStyle = "#ff9800";
|
||||
context.stroke();
|
||||
context.restore();
|
||||
return;
|
||||
}
|
||||
const style = getComputedStyle(document.documentElement);
|
||||
const tone = (cut: boolean | undefined): string =>
|
||||
cut === undefined
|
||||
? CROSS_WIDTH_COLOR
|
||||
: style.getPropertyValue(cut ? "--b05-footprint-cut" : "--b05-footprint-fill").trim() ||
|
||||
(cut ? "#dc2626" : "#2563eb");
|
||||
const ends: Array<[Vertex, string]> = [
|
||||
[[ox + lx * watch.leftM, oy + ly * watch.leftM], tone(watch.leftCut)],
|
||||
[[ox - lx * watch.rightM, oy - ly * watch.rightM], tone(watch.rightCut)],
|
||||
];
|
||||
context.lineWidth = 2.4;
|
||||
ends.forEach((end) => {
|
||||
ends.forEach(([end, color]) => {
|
||||
const [x, y] = scene.toScreen(end);
|
||||
context.strokeStyle = color;
|
||||
context.fillStyle = color;
|
||||
context.beginPath();
|
||||
context.moveTo(cx, cy);
|
||||
context.lineTo(x, y);
|
||||
@@ -426,6 +447,11 @@ function drawCrossWidth(
|
||||
context.arc(x, y, 3, 0, Math.PI * 2);
|
||||
context.fill();
|
||||
});
|
||||
context.strokeStyle = CROSS_WIDTH_COLOR;
|
||||
context.lineWidth = 2;
|
||||
context.beginPath();
|
||||
context.arc(cx, cy, 6, 0, Math.PI * 2);
|
||||
context.stroke();
|
||||
context.restore();
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@ const store = load("A00_Common", "design", "design_store.ts");
|
||||
// B05 오른쪽 횡단 판(365-3) — 그림 조각만 가짜 · 값 고르기는 진짜
|
||||
STUBS["b_svg_zoom_pan"] = { attachSvgZoomPan: () => undefined };
|
||||
STUBS["B05_Profile_UI_RouteEdit_Cross_Draw"] = { CROSS_PLOT_PAD: {}, summarizeCross: () => "" };
|
||||
STUBS["B05_Profile_UI_RouteEdit_Chrome"] = { sidePanelsOf: new WeakMap() }; // 판 양식(382-1) — 화면 밖
|
||||
const pane = load("B05_Profile", "B05_Profile_UI_RouteEdit_Cross.ts");
|
||||
const r3 = (value) => (typeof value === "number" ? Math.round(value * 1000) / 1000 : null);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* 계획노선 편집 창 **측점 반폭 선 길이**(계획서 22-1) 단위검증 헬퍼 —
|
||||
/* 계획노선 편집 창 **측점 법선 · 고름 연동**(계획서 22-1 · 382-2) 단위검증 헬퍼 —
|
||||
* `B05_Profile_UI_RouteEdit_Cross.ts` 를 트랜스파일해 Node 로 돌린다.
|
||||
* 계산 묶음 · 그림 조각은 가짜로 채운다 — 「무슨 반폭으로 선을 잡나」만 본다(365-3 · 서버 길 없음 ·
|
||||
* 묶음 그 측점 원지반 표본 = 좌우 20 m).
|
||||
* 계산 묶음 · 가게 · 그림 조각은 가짜로 채운다 — 공용 고름(`design_selection`)은 진짜.
|
||||
* 묶음 측점 = 일반측점 grid:2(40) · grid:3(60) · grid:5(100) · 관 pipe:a(80) · 원지반 표본 좌우 20 m.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
@@ -57,6 +57,19 @@ globalThis.ResizeObserver = class {
|
||||
disconnect() {}
|
||||
};
|
||||
|
||||
const grid = (k, chainageM) => ({ key: `grid:${k}`, kind: "grid", chainageM });
|
||||
let stations = [
|
||||
grid(2, 40),
|
||||
grid(3, 60),
|
||||
{ key: "pipe:a", kind: "pipe", chainageM: 80 },
|
||||
grid(5, 100),
|
||||
];
|
||||
let footprint = null;
|
||||
let route = { v: 1 };
|
||||
// 접힌 판 펴기 — 몸(side)에 묶인 패널 묶음 흉내
|
||||
const panels = { now: null, current: () => panels.now, open: (id) => (panels.now = id) };
|
||||
const side = { append() {} };
|
||||
|
||||
const STUBS = {
|
||||
"../A00_Common/b_svg_zoom_pan": { attachSvgZoomPan: () => {} },
|
||||
"../common_util/common_util_station": { stationLabel: (m) => String(m) },
|
||||
@@ -65,17 +78,19 @@ const STUBS = {
|
||||
CROSS_PLOT_PAD: 0,
|
||||
summarizeCross: () => "",
|
||||
},
|
||||
// 계산 묶음 흉내 — 측점 하나(100 m) · 원지반 표본 좌우 20 m
|
||||
"./B05_Profile_UI_RouteEdit_Chrome": { sidePanelsOf: new WeakMap([[side, panels]]) },
|
||||
"../A00_Common/design/design_pipeline_page": {
|
||||
pagePipeline: () => ({
|
||||
output: (name) =>
|
||||
name === "ground" ? { stations: [{ key: "grid:5", chainageM: 100 }] } : null,
|
||||
input: () => null,
|
||||
output: (name) => (name === "ground" ? { stations } : null),
|
||||
input: () => route,
|
||||
subscribe: () => () => undefined,
|
||||
}),
|
||||
},
|
||||
"../A00_Common/design/design_store": {
|
||||
useDesignStore: () => ({ subscribe: () => () => undefined }),
|
||||
useDesignStore: () => ({
|
||||
subscribe: () => () => undefined,
|
||||
get: (name) => (name === "footprint" ? footprint : null),
|
||||
}),
|
||||
},
|
||||
"./logic/B05_Profile_Pipeline_Detail": {
|
||||
stationSection: (station) => ({
|
||||
@@ -108,12 +123,26 @@ function loadTs(file) {
|
||||
const { createCrossPreview } = loadTs(
|
||||
path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Cross.ts"),
|
||||
);
|
||||
const pick = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts"));
|
||||
|
||||
const record = (key, chainage_m, left, right, leftCut, rightCut) => ({
|
||||
key,
|
||||
chainage_m,
|
||||
left_edge_m: 2.5,
|
||||
left_end_m: left,
|
||||
right_edge_m: -2.5,
|
||||
right_end_m: right,
|
||||
left_cut: leftCut,
|
||||
right_cut: rightCut,
|
||||
});
|
||||
|
||||
async function run() {
|
||||
let shownCount = 0;
|
||||
const cross = createCrossPreview({
|
||||
projectId: "p-1",
|
||||
side: { append() {} },
|
||||
side,
|
||||
request: () => ({ vertices: [], min_radius_m: 12, station_interval_m: 20 }),
|
||||
onShown: () => (shownCount += 1),
|
||||
});
|
||||
const out = {};
|
||||
out.before = cross.watched();
|
||||
@@ -129,6 +158,47 @@ async function run() {
|
||||
session.setItem("aislo:draft:cross-display:p-1:12", "abc");
|
||||
await cross.refresh();
|
||||
out.badValue = { watched: cross.watched() };
|
||||
|
||||
// 382-2 발자국 기록 = 비탈 끝 좌 · 우 · 깎기 여부
|
||||
footprint = { records: [record("grid:5", 100, 7.5, -6, true, false)] };
|
||||
out.footprint = cross.watched();
|
||||
// 노선 고침 — 같은 열쇠 측점이 102 로 밀리고 발자국도 새로(열쇠로 다시 짚음)
|
||||
stations = [
|
||||
grid(2, 40),
|
||||
grid(3, 60),
|
||||
{ key: "pipe:a", kind: "pipe", chainageM: 80 },
|
||||
grid(5, 102),
|
||||
];
|
||||
footprint = { records: [record("grid:5", 102, 9, -4, false, true)] };
|
||||
route = { v: 2 };
|
||||
await cross.refresh();
|
||||
out.afterEdit = cross.watched();
|
||||
|
||||
// 평면 눈금 클릭 = 공용 고름에 적음 · 판이 접혀 있으면 폄
|
||||
const heard = [];
|
||||
pick.subscribeSelection("p-1", (selection, from) =>
|
||||
heard.push({ at: selection.chainageM, from }),
|
||||
);
|
||||
panels.now = null;
|
||||
await cross.open(41);
|
||||
out.planClick = {
|
||||
selection: pick.readSelection("p-1"),
|
||||
heard: heard.slice(),
|
||||
panel: panels.now,
|
||||
watched: cross.watched(),
|
||||
};
|
||||
|
||||
// 다른 보기 고름 — 일반측점 = 그 측점(판 폄) · 관 측점 · 구조물 id = 마름모 · 풂 = 없음
|
||||
panels.now = null;
|
||||
pick.selectDesign("p-1", { chainageM: 60, structureId: null }, { from: "b06-card" });
|
||||
out.otherGrid = { watched: cross.watched(), panel: panels.now };
|
||||
pick.selectDesign("p-1", { chainageM: 80, structureId: null }, { from: "profile" });
|
||||
out.otherPipe = cross.watched();
|
||||
pick.selectDesign("p-1", { chainageM: 60, structureId: "s-1" }, { from: "list" });
|
||||
out.otherStructure = cross.watched();
|
||||
const before = shownCount;
|
||||
pick.selectDesign("p-1", { chainageM: null, structureId: null }, { from: "escape" });
|
||||
out.cleared = { watched: cross.watched(), redrawn: shownCount > before };
|
||||
return out;
|
||||
}
|
||||
run().then((out) => console.log(JSON.stringify(out)));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""계획노선 편집 창 — **측점 반폭 선 길이**(계획서 22-1) 회귀검증.
|
||||
"""계획노선 편집 창 — **측점 법선 길이 · 고름 연동**(계획서 22-1 · 382-2) 회귀검증.
|
||||
|
||||
파란 선 = 우측 패널 「횡단 반폭(m)」 적용값 양쪽. 적용한 적이 없으면 원지반 선 범위
|
||||
법선 = 그 측점 발자국 비탈 끝 좌 · 우(382-2) · 기록이 없으면 「횡단 반폭(m)」 적용값 양쪽 · 그것도 없으면 원지반 선 범위
|
||||
(계산 묶음 그 측점 표본 · 365-3).
|
||||
Node 헬퍼(`helper_b05_routeedit_cross_width.cjs`)가 TS 를 트랜스파일해 돌린다.
|
||||
"""
|
||||
@@ -45,3 +45,50 @@ def test_노선이_여럿이면_최신_노선_값_다른_프로젝트는_무시(
|
||||
|
||||
def test_망가진_값은_기본값으로(results):
|
||||
assert results["badValue"]["watched"]["rightM"] == 20
|
||||
|
||||
|
||||
# ── 382-2 법선 = 발자국 비탈 끝 · 측점 열쇠 · 공용 고름 연동 ──────────────────────
|
||||
|
||||
|
||||
def test_발자국_기록이면_비탈_끝_좌우와_깎기(results):
|
||||
assert results["footprint"] == {
|
||||
"chainageM": 100,
|
||||
"leftM": 7.5,
|
||||
"rightM": 6,
|
||||
"leftCut": True,
|
||||
"rightCut": False,
|
||||
}
|
||||
|
||||
|
||||
def test_노선_고친_뒤_같은_열쇠_측점의_새_발자국(results):
|
||||
assert results["afterEdit"] == {
|
||||
"chainageM": 102,
|
||||
"leftM": 9,
|
||||
"rightM": 4,
|
||||
"leftCut": False,
|
||||
"rightCut": True,
|
||||
}
|
||||
|
||||
|
||||
def test_평면_눈금_클릭은_공용_고름에_적고_판을_편다(results):
|
||||
click = results["planClick"]
|
||||
assert click["selection"] == {"chainageM": 40, "structureId": None}
|
||||
assert click["heard"] == [{"at": 40, "from": "b05-plan"}]
|
||||
assert click["panel"] == "cross"
|
||||
assert click["watched"]["chainageM"] == 40
|
||||
|
||||
|
||||
def test_다른_보기_일반측점_고름은_평면_판(results):
|
||||
assert results["otherGrid"]["watched"]["chainageM"] == 60
|
||||
assert "structure" not in results["otherGrid"]["watched"]
|
||||
assert results["otherGrid"]["panel"] == "cross"
|
||||
|
||||
|
||||
def test_관_구조물_고름은_마름모만(results):
|
||||
assert results["otherPipe"] == {"chainageM": 80, "leftM": 0, "rightM": 0, "structure": True}
|
||||
assert results["otherStructure"]["structure"] is True
|
||||
assert results["otherStructure"]["chainageM"] == 60
|
||||
|
||||
|
||||
def test_고름을_풀면_법선도_없다(results):
|
||||
assert results["cleared"] == {"watched": None, "redrawn": True}
|
||||
|
||||
Reference in New Issue
Block a user