Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_4

This commit is contained in:
2026-10-30 14:47:33 +09:00
8 changed files with 228 additions and 91 deletions
+10 -7
View File
@@ -42,13 +42,15 @@ const GRID_M = 2;
* 잇는 띠를 1.5~2.5 m 로 늘림. 변 판정(boundary.touches)은 그대로. */
export const SEAM_M = 1.5;
/** 노면부 띠 — 행마다 좌우 노면 끝 사이 사각형(리본이 덮는 자리 · 망에서 뺀다). */
/** 노면부 띠 — 행마다 좌우 노면 끝 사이 사각형(리본이 덮는 자리 · 망에서 뺀다) · 여러 벌이면 띠 전부. */
class RoadStrip extends CellRegion {
constructor(road: TinDesign["road"]) {
constructor(roads: ReadonlyArray<TinDesign["road"]>) {
super();
const p = ([x, z]: [number, number]) => ({ x, z });
for (let i = 0; i + 1 < road.left.length; i += 1) {
this.addCell(p(road.left[i]), p(road.right[i]), p(road.right[i + 1]), p(road.left[i + 1]));
for (const road of roads) {
for (let i = 0; i + 1 < road.left.length; i += 1) {
this.addCell(p(road.left[i]), p(road.right[i]), p(road.right[i + 1]), p(road.left[i + 1]));
}
}
this.buildIndex();
}
@@ -199,12 +201,13 @@ export function tinTerrain(
const design = designer(build, groundAt, bounds);
if (!design) return null;
const outer = new LoopIndex([design.outer]);
const outers = design.outers ?? [design.outer];
const outer = new LoopIndex(outers);
const holes = new LoopIndex(design.holes);
const inRegion = (x: number, z: number): boolean => outer.contains(x, z) || holes.contains(x, z);
const boundary = new SegGrid(GRID_M);
let segment = 0;
[design.outer, ...design.holes].forEach((loop) => {
[...outers, ...design.holes].forEach((loop) => {
for (let i = 0, j = loop.length - 1; i < loop.length; j = i, i += 1) {
boundary.add(segment, loop[j][0], loop[j][1], loop[i][0], loop[i][1]);
segment += 1;
@@ -276,7 +279,7 @@ export function tinTerrain(
}
const { triangles, failed } = pslg.triangulate();
const road = new RoadStrip(design.road);
const road = new RoadStrip(design.roads ?? [design.road]);
const positions: number[] = [];
const colors: number[] = [];
const keep: number[] = [];
+14
View File
@@ -253,6 +253,19 @@ export function createRouteMarkers(
warnings: Array<{ polyline_start_index: number; polyline_end_index: number }> = [],
): void {
disposeGroup(routeGroup);
drawRoute(polyline, warnings);
}
/** 여러 선(D03 노선망 · PLAN 601-2) — 앞 선들을 버리고 한꺼번에 · 경고 구간 없음. */
function renderRoutes(polylines: Array<Array<{ x: number; y: number; z?: number }>>): void {
disposeGroup(routeGroup);
polylines.forEach((polyline) => drawRoute(polyline, []));
}
function drawRoute(
polyline: Array<{ x: number; y: number; z?: number }>,
warnings: Array<{ polyline_start_index: number; polyline_end_index: number }>,
): void {
const bounds = getBounds();
if (!bounds || polyline.length < 2) return;
// 정점 사이를 잘게 나눠 **각 점의 지형고를 찍어** 선을 지면에 붙인다(2026-09-06).
@@ -551,6 +564,7 @@ export function createRouteMarkers(
},
renderMarkers,
renderRoute,
renderRoutes,
renderStationLines,
selectStation,
setStationLinesVisible(visible: boolean) {
+3
View File
@@ -82,6 +82,8 @@ export interface RouteViewer {
* 이 호출 뒤에 할 것. null이면 코리도 제거.
*/
setCorridor: (build: CorridorBuildResult | null) => void;
/** 여러 벌(D03 노선망 · PLAN 601-2) — 벌마다 노면 · 지형 잘림 한 번 · 빈 배열 = 지움. */
setCorridors: (builds: CorridorBuildResult[]) => void;
/** [예상형상] 토글 — ON: 클리핑 지형+코리도(공사 후), OFF: 원본 지형 완전체. */
setCorridorVisible: (visible: boolean) => void;
renderStationLines: (stations: SectionStationMarker[], halfWidth: number) => void;
@@ -494,6 +496,7 @@ export function createRouteViewer(): RouteViewer {
corridor.setSurfaceVisible(visible);
},
setCorridor: corridor.set,
setCorridors: corridor.setMany,
setCorridorVisible: corridor.setVisible,
setSurfaceGrayscale(grayscale) {
if (surfaceGrayscale === grayscale) return;
+97 -29
View File
@@ -3,6 +3,8 @@
* 3D 뷰어의 예상형상(코리도) 조각 — 원본/예상형상 지형 스왑 + 계획 서피스 조합.
* 예상형상 = 제안 ③(측점 횡단 기반 사면 · `_Loft.ts`) 하나 · 노면부만 코리도로 그린다.
* ③ 을 못 만들면(높이 색인 없음 · 측점 부족) 원지형 그대로.
* 여러 벌(D03 노선망 · PLAN 601-2 `setMany`) = 벌마다 노면 그룹 · 지형 잘림은 벌마다 설계를 한 PSLG 로 합쳐 한 번에.
* 한 벌(`set`)은 옛 길 그대로(B05 화면 동작 같음).
*
* 뷰어 본체(`B05_Profile_UI_Viewer.ts`)가 700줄을 넘어 이 부분만 뗐다(계획서 24-3a).
* 지형 · 범위 · 높이 색인은 뷰어가 쥐고, 여기서는 받아 읽기만 한다.
@@ -15,7 +17,11 @@ import { createCorridorGroup } from "./B05_Profile_UI_Corridor_Mesh";
import { setCorridorBuildSummary } from "./B05_Profile_UI_Viewer_Debug";
import type { TerrainHeightIndex } from "./B05_Profile_UI_Corridor_Terrain";
import { BAND_MARGIN_M, TerrainBandSplit, type SceneBox } from "./B05_Profile_UI_Corridor_Split";
import { loftTerrain } from "./B05_Profile_UI_Corridor_Loft";
import { loftDesign, loftTerrain } from "./B05_Profile_UI_Corridor_Loft";
import { smoothSlopeNormals } from "./B05_Profile_UI_Corridor_Loft_Shade";
import { tinTerrain, type TinDesigner } from "./B05_Profile_UI_Corridor_Tin";
import type { TinDesign } from "./logic/B05_Profile_UI_Corridor_Tin_Design";
import { Pslg } from "./logic/B05_Profile_UI_Corridor_Tin_Pslg";
/** 서피스 삼각형 수 — 예상형상 지형이 실제로 바뀌었는지 확인하는 계측용. */
function countTriangles(root: THREE.Object3D | null): number {
@@ -81,6 +87,8 @@ export interface ViewerCorridorDeps {
export interface ViewerCorridor {
set: (build: CorridorBuildResult | null) => void;
/** 여러 벌 — 빈 배열 = 지움 · 한 벌 = `set` 과 같음. */
setMany: (builds: CorridorBuildResult[]) => void;
setVisible: (visible: boolean) => void;
setSurfaceVisible: (visible: boolean) => void;
/** 지형이 바뀌기 전 — 밴드 분할본을 버린다. */
@@ -100,9 +108,57 @@ function roadOnly(build: CorridorBuildResult): CorridorBuildResult {
};
}
/** 벌마다 설계를 한 PSLG 로 — 점 · 변 옮겨 담고 벌 사이 교차를 다시 나눔 · 둘레 · 띠 = 벌마다(합집합). */
function mergeDesigns(designs: TinDesign[]): TinDesign | null {
if (designs.length <= 1) return designs[0] ?? null;
const pslg = new Pslg();
const stats = { rows: 0, sections: 0, bowtie: 0, bendLines: 0, holes: 0, splits: 0 };
for (const design of designs) {
const from = design.pslg;
const ids = from.x.map((x, i) => pslg.addPoint(x, from.z[i], from.y[i], from.tag[i]));
from.kinds.forEach((kind, e) =>
pslg.addEdge(ids[from.edges[e * 2]], ids[from.edges[e * 2 + 1]], kind),
);
(Object.keys(stats) as Array<keyof typeof stats>).forEach((key) => {
stats[key] += design.stats[key];
});
}
stats.splits += pslg.splitCrossings();
return {
pslg,
outer: designs[0].outer,
holes: designs.flatMap((design) => design.holes),
road: designs[0].road,
outers: designs.map((design) => design.outer),
roads: designs.map((design) => design.road),
stats,
};
}
/** 여러 벌 제안 ③ 지형 — 벌마다 `loftDesign` → 합침 → TIN 한 번. */
function loftTerrainMany(
split: TerrainBandSplit,
builds: CorridorBuildResult[],
bounds: ModelBounds,
heightIndex: TerrainHeightIndex,
): ReturnType<typeof loftTerrain> {
const usable = builds.filter((build) => build.sections?.length);
if (!usable.length) return null;
const designer: TinDesigner = (_build, groundAt, box) =>
mergeDesigns(
usable.flatMap((build) => {
const design = loftDesign(build, groundAt, box);
return design ? [design] : [];
}),
);
return tinTerrain(split, usable[0], bounds, heightIndex, designer, (mesh) =>
smoothSlopeNormals(mesh),
);
}
export function createViewerCorridor(deps: ViewerCorridorDeps): ViewerCorridor {
const { scene } = deps;
let corridorBuild: CorridorBuildResult | null = null;
let corridorBuilds: CorridorBuildResult[] = [];
let corridorGroup: THREE.Group | null = null;
let clippedTerrain: THREE.Object3D | null = null;
/** 지형 near/far 분할본 — 편집마다 노선 주변만 재트림하려고 1회만 만든다. */
@@ -110,7 +166,7 @@ export function createViewerCorridor(deps: ViewerCorridorDeps): ViewerCorridor {
let corridorOn = true; // [예상형상] 기본 ON — 계획서피스가 보이는 게 기본값.
let surfaceOn = true; // 기존 [지표면] 토글 상태(코리도 스왑과 조합).
/** 코리도 그룹에 그린 빌드(노면부만). */
let drawnBuild: CorridorBuildResult | null = null;
let drawnBuilds: CorridorBuildResult[] = [];
/** 표시 상태 일괄 적용 — 클리핑본이 준비되기 전에는 원본 지형을 그대로 둔다. */
function applyCorridorVisibility(): void {
@@ -133,8 +189,8 @@ export function createViewerCorridor(deps: ViewerCorridorDeps): ViewerCorridor {
clippedTerrain = null;
}
/** 코리도를 넉넉히 감싼 밴드(씬 좌표) — 이 안쪽만 편집마다 다시 트림한다. */
function corridorBand(build: CorridorBuildResult): SceneBox | null {
/** 코리도를 넉넉히 감싼 밴드(씬 좌표) — 이 안쪽만 편집마다 다시 트림한다(여러 벌 = 모두 감쌈). */
function corridorBand(builds: CorridorBuildResult[]): SceneBox | null {
const bounds = deps.bounds();
if (!bounds) return null;
const ox = (bounds.x[0] + bounds.x[1]) / 2;
@@ -143,23 +199,25 @@ export function createViewerCorridor(deps: ViewerCorridorDeps): ViewerCorridor {
let maxX = -Infinity;
let minZ = Infinity;
let maxZ = -Infinity;
[...build.outline.left, ...build.outline.right].forEach(([mx, my]) => {
const x = mx - ox;
const z = -(my - oy);
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (z < minZ) minZ = z;
if (z > maxZ) maxZ = z;
});
builds
.flatMap((build) => [...build.outline.left, ...build.outline.right])
.forEach(([mx, my]) => {
const x = mx - ox;
const z = -(my - oy);
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (z < minZ) minZ = z;
if (z > maxZ) maxZ = z;
});
if (!Number.isFinite(minX)) return null;
return { minX, maxX, minZ, maxZ };
}
/** 밴드 분할본 확보 — 코리도가 밴드를 벗어났을 때만 다시 가른다. */
function ensureBandSplit(build: CorridorBuildResult): TerrainBandSplit | null {
function ensureBandSplit(builds: CorridorBuildResult[]): TerrainBandSplit | null {
const terrain = deps.terrain();
if (!terrain) return null;
const box = corridorBand(build);
const box = corridorBand(builds);
if (!box) return null;
if (bandSplit?.covers(box)) return bandSplit;
bandSplit?.dispose();
@@ -182,21 +240,26 @@ export function createViewerCorridor(deps: ViewerCorridorDeps): ViewerCorridor {
/** 클리핑본 재생성 — 무거우므로 코리도 표시 후 다음 프레임에 수행(비동기 스왑). */
function scheduleTerrainClip(): void {
disposeClippedTerrain();
if (!corridorBuild || !deps.terrain() || !deps.bounds()) {
if (!corridorBuilds.length || !deps.terrain() || !deps.bounds()) {
applyCorridorVisibility();
return;
}
const buildAtSchedule = corridorBuild;
const buildsAtSchedule = corridorBuilds;
requestAnimationFrame(() => {
const terrain = deps.terrain();
const bounds = deps.bounds();
// 예약 사이에 코리도가 교체·제거됐으면 이 클립은 폐기한다.
if (corridorBuild !== buildAtSchedule || !terrain || !bounds) return;
const drawn = drawnBuild ?? buildAtSchedule;
if (corridorBuilds !== buildsAtSchedule || !terrain || !bounds) return;
const [buildAtSchedule] = buildsAtSchedule;
const drawn = drawnBuilds.length ? drawnBuilds : buildsAtSchedule;
const split = ensureBandSplit(drawn);
if (!split) return;
const heightIndex = deps.heightIndex();
const shaped = heightIndex ? loftTerrain(split, buildAtSchedule, bounds, heightIndex) : null;
const shaped = !heightIndex
? null
: buildsAtSchedule.length === 1
? loftTerrain(split, buildAtSchedule, bounds, heightIndex)
: loftTerrainMany(split, buildsAtSchedule, bounds, heightIndex);
(window as unknown as { __corridorDeform?: unknown }).__corridorDeform =
shaped?.stats ?? null;
disposeClippedTerrain();
@@ -221,14 +284,18 @@ export function createViewerCorridor(deps: ViewerCorridorDeps): ViewerCorridor {
applyCorridorVisibility();
}
function set(build: CorridorBuildResult | null): void {
function setMany(builds: CorridorBuildResult[]): void {
disposeCorridorGroup();
corridorBuild = build;
corridorBuilds = builds;
const bounds = deps.bounds();
drawnBuild = build && bounds ? roadOnly(build) : null;
setCorridorBuildSummary(drawnBuild ?? build);
if (drawnBuild && bounds) {
corridorGroup = createCorridorGroup(drawnBuild, bounds);
drawnBuilds = bounds ? builds.map(roadOnly) : [];
setCorridorBuildSummary(drawnBuilds[0] ?? builds[0] ?? null);
if (drawnBuilds.length && bounds) {
if (drawnBuilds.length === 1) corridorGroup = createCorridorGroup(drawnBuilds[0], bounds);
else {
corridorGroup = new THREE.Group();
drawnBuilds.forEach((drawn) => corridorGroup!.add(createCorridorGroup(drawn, bounds)));
}
scene.add(corridorGroup);
deps.onGroup();
}
@@ -236,7 +303,8 @@ export function createViewerCorridor(deps: ViewerCorridorDeps): ViewerCorridor {
}
return {
set,
set: (build) => setMany(build ? [build] : []),
setMany,
setVisible(visible) {
corridorOn = visible;
applyCorridorVisibility();
@@ -250,12 +318,12 @@ export function createViewerCorridor(deps: ViewerCorridorDeps): ViewerCorridor {
bandSplit = null;
},
rebuild() {
if (corridorBuild) set(corridorBuild);
if (corridorBuilds.length) setMany(corridorBuilds);
},
group: () => corridorGroup,
clipped: () => clippedTerrain,
dispose() {
corridorBuild = null; // 예약된 클립 콜백 무효화.
corridorBuilds = []; // 예약된 클립 콜백 무효화.
disposeCorridorGroup();
disposeClippedTerrain();
bandSplit?.dispose();
@@ -24,6 +24,9 @@ export interface TinDesign {
holes: Array<Array<[number, number]>>;
/** 노면부 띠 행(씬 평면) — 좌 · 우 노면 끝. */
road: { left: Array<[number, number]>; right: Array<[number, number]> };
/** 여러 벌(D03 노선망 · PLAN 601-2) — 있으면 `outer` · `road` 대신 이 둘레 · 띠 전부(합집합). */
outers?: Array<Array<[number, number]>>;
roads?: Array<TinDesign["road"]>;
stats: {
rows: number;
sections: number;
+97 -50
View File
@@ -1,25 +1,47 @@
/* =============================================================================
* D03_PlanAnalysis_UI_3D.ts
* D03 [3D] 큰 모달(PLAN 530-3 · 563-1) — 사업 전체 DEM + 그은 선 하나(지반에 붙임) + 측점 막대.
* 선 고름 = 모달 위 칸(본선 · 갈래) · 뷰어 = B05 `createRouteViewer` 그대로 · 닫으면 버림(WebGL 문맥까지).
* 예상 코리도 없음(꺾은선 · 계획고 = 지반고라 절성토 0 — 요구서 389 3장 11).
* D03 [3D] 큰 모달(PLAN 530-3 · 563-1 · 601-3 · 요구 463-21) — 사업 전체 DEM + 노선망 전체(본선 · 갈래) 한 번에.
* 선마다 예상 코리도 = 측점 예상 단면(`logic/D03_PlanAnalysis_Expect` · 리핑암 · 산쪽 절토 · 일반 측구 ·
* 계획고 = 지반고) → B05 `buildCorridor` → 뷰어 `setCorridors`(벌마다 노면 · 지형 잘림 한 번) ·
* 갈래 머리 = 부모 노면만큼 잘라 냄 · 뷰어 = B05 `createRouteViewer` · 닫으면 버림(WebGL 문맥까지) · 보기만(저장 없음).
* ========================================================================== */
import "../B05_Profile/B05_Profile_UI_Style.css";
import { createSelectField } from "@ui/ui_template_input";
import { openModal } from "@ui/ui_template_modal";
import { createProgressCircle } from "@ui/ui_template_progress";
import { showToast } from "@ui/ui_template_elements";
import type { DtmGrid } from "../common_util/common_util_surface_sampler";
import { createRouteViewer, type RouteViewer } from "../B05_Profile/B05_Profile_UI_Viewer";
import { sectionsGround } from "../B05_Profile/logic/B05_Profile_Sections_Ground";
import { buildCorridor } from "../B05_Profile/logic/B05_Profile_Corridor_Build";
import type { CorridorBuildResult } from "../B05_Profile/logic/B05_Profile_Corridor_Build";
import type { SectionStationMarker } from "../B05_Profile/B05_Profile_UI_Markers";
import { fetchSectionContext } from "../B06_Section/B06_Section_Api_Context";
import type { CrossSection } from "../B06_Section/B06_Section_Api_Types";
import type { Vertex } from "./logic/D03_PlanAnalysis_Plan";
import {
branchTrimM,
expectLine,
expectSections,
type ExpectStandard,
} from "./logic/D03_PlanAnalysis_Expect";
export interface Plan3dLine {
name: string;
line: Vertex[];
/** 선 위 측점 누가(선마다 No.0 부터). */
stations: number[];
/** 갈래(다른 선에 붙은 선) — 머리를 부모 노면만큼 잘라 지음. */
branch: boolean;
}
/** 표준 횡단 기본값(config · 서버) — 못 받으면 null(선만). */
function loadExpectStandard(projectId: string): Promise<ExpectStandard | null> {
return fetchSectionContext(projectId).then(
(context) => ({
standard: context.standard_cross_section as ExpectStandard["standard"],
rockBoundaryM: context.rock_boundary_default_offset_m,
}),
() => null,
);
}
/** 격자 → 뷰어 상자(지형에 붙는 선 · 막대는 높이 원점과 무관 · x · y 만 메시와 맞으면 됨). */
@@ -46,58 +68,84 @@ export function openPlan3d(
projectId: string,
surface: { modelId: number; grid: DtmGrid },
lines: Plan3dLine[],
intervalM: number,
): void {
if (!lines.length) return void showToast("3D 로 볼 선이 없습니다.", "warning");
let viewer: RouteViewer | null = createRouteViewer();
let loaded = false;
const pick = createSelectField({
options: lines.map((item, i) => ({ value: String(i), text: item.name })),
value: "0",
size: 28,
ariaLabel: "선",
}).select;
pick.classList.add("d03-plan__3d-year");
const top = document.createElement("div");
top.className = "d03-plan__3d-top";
top.append(pick);
const standard = loadExpectStandard(projectId);
const load = async (index: number): Promise<void> => {
const item = lines[index] ?? lines[0];
const load = async (): Promise<void> => {
if (!viewer) return;
const progress = createProgressCircle({ label: "3D 지형을 불러오는 중…", overlay: true });
progress.set(null);
viewer.root.append(progress.root);
const started = performance.now();
const stat: Record<string, unknown> = { line: item.name, done: false };
const stat: Record<string, unknown> = { lines: lines.length, done: false };
(window as unknown as { __d03Plan3d?: unknown }).__d03Plan3d = stat;
try {
if (!loaded) {
await viewer.loadSurface(
projectId,
surface.modelId,
"dtm",
true,
5,
gridBounds(surface.grid),
);
loaded = true;
}
const [std] = await Promise.all([
standard,
viewer.loadSurface(projectId, surface.modelId, "dtm", true, 5, gridBounds(surface.grid)),
]);
if (!viewer) return;
viewer.markers.renderRoute(item.line.map(([x, y]) => ({ x, y })));
const ground = sectionsGround(item.line, item.stations, surface.grid);
viewer.renderStationLines(
ground.stations.map((station, i) => ({
station_id: String(item.stations[i] ?? station.chainage_m),
center_x: station.center_x,
center_y: station.center_y,
center_z: station.center_z,
frame: { left_xy: station.left_xy },
kind: "regular",
chainage_m: item.stations[i] ?? station.chainage_m,
})),
2.5,
);
stat.stations = ground.stations.length;
stat.surfaceMs = Math.round(performance.now() - started);
viewer.markers.renderRoutes(lines.map((item) => item.line.map(([x, y]) => ({ x, y }))));
const built = performance.now();
const trim = std ? branchTrimM(std) : 0;
const markers: SectionStationMarker[] = [];
const builds: CorridorBuildResult[] = [];
const perLine: Array<Record<string, unknown>> = [];
lines.forEach((item, index) => {
const plan = expectLine(item.line, item.branch ? trim : 0, intervalM);
if (!plan) return;
const ground = sectionsGround(plan.line, plan.stations, surface.grid);
ground.stations.forEach((station, i) => {
const chainage = (plan.stations[i] ?? station.chainage_m) + plan.offsetM;
markers.push({
station_id: `${index}:${chainage}`,
center_x: station.center_x,
center_y: station.center_y,
center_z: station.center_z,
frame: { left_xy: station.left_xy },
kind: "regular",
chainage_m: chainage,
});
});
if (!std) return;
const sections = expectSections(
ground.stations,
std,
surface.grid,
) as unknown as CrossSection[];
const build = buildCorridor(
sections,
plan.line.map(([x, y]) => ({ x, y })),
);
if (build) {
build.sections = sections; // 지형 잘림이 읽음
builds.push(build);
}
perLine.push({
name: item.name,
branch: item.branch,
offsetM: plan.offsetM,
sections: sections.length,
corridor: !!build,
// 노면 중심(설계선 오프셋 0) − 측점 지반고
centerDelta: sections.map((c) =>
c.center_z === null ? null : (c.design?.design_elevation_m ?? NaN) - c.center_z,
),
});
});
viewer.renderStationLines(markers, 2.5);
viewer.setCorridors(builds);
Object.assign(stat, {
trimM: trim,
corridors: builds.length,
stations: markers.length,
buildMs: Math.round(performance.now() - built),
perLine,
});
viewer.setView("iso");
} catch (error) {
showToast(error instanceof Error ? error.message : "3D 지형을 불러오지 못했습니다.", "error");
@@ -107,16 +155,15 @@ export function openPlan3d(
progress.remove();
}
};
pick.addEventListener("change", () => void load(Number(pick.value)));
openModal({
title: "3D 보기 — 지형 + 그은 선",
title: "3D 보기 — 지형 + 노선망(표준 횡단)",
closeLabel: "닫기",
wide: true,
dialogClass: "d03-plan__3d-modal",
mount: (body) => {
body.classList.add("d03-plan__3d-body");
body.append(top, viewer!.root);
body.append(viewer!.root);
viewer!.setActive(true);
},
beforeClose: () => {
@@ -125,5 +172,5 @@ export function openPlan3d(
return true;
},
});
void load(0);
void load();
}
+2 -3
View File
@@ -58,7 +58,6 @@ import { createPlanMeasure } from "./D03_PlanAnalysis_UI_Measure";
import { createPlanPoints } from "./D03_PlanAnalysis_UI_Points";
import { createPlanPieces } from "./D03_PlanAnalysis_UI_Pieces";
import { createPlanProps } from "./D03_PlanAnalysis_UI_Props";
import { lineLength, stationChainages } from "./logic/D03_PlanAnalysis_Draw";
import {
emptyNetwork,
fromServer,
@@ -501,10 +500,10 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
.map((line, index) => ({
name: names[index],
line: line.vertices,
stations: stationChainages(lineLength(line.vertices), PLAN_STATION_INTERVAL_M),
branch: line.branch !== null,
}))
.filter((item) => item.line.length > 1);
openPlan3d(projectId, surface, lines);
openPlan3d(projectId, surface, lines, PLAN_STATION_INTERVAL_M);
}
// 검증 창구 — 꼴 · 미리보기 · 제약 · 화면 자리 · 재기 · 회전 · 저장 시간(ORCA 판정)
+2 -2
View File
@@ -80,8 +80,8 @@ def test_d03_parts():
props = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Props.ts") # 585-2 조각 연도 = 속성 판
assert "createSelectField(" in props and "createFloatPanel(" in props
assert 'createElement("button")' not in props and 'createElement("select")' not in props
assert 'createElement("select")' not in three and "createSelectField" in three
assert "createCheckbox" not in three and "buildCorridor" not in three # 예상 코리도 걷음
assert 'createElement("select")' not in three and "createSelectField" not in three # 601 선 고름 걷음
assert "createCheckbox" not in three and "setCorridors(" in three # 601 노선망 예상 코리도 한 번에
for dead in (".d03-plan__bar", ".d03-plan__mode", "is-picked"):
assert dead not in css, dead
assert "b05-route__3d-modal" not in three and "b05-route__3d-body" not in three # 빌림 끊음