Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
/* 346-2 4파 헬퍼 — 측점 셋 늘고 둘 빠진 목록으로 상세 갈아끼움 · 카드 수 · 열쇠 이어 받기 · 보이는 카드 먼저.
|
||||
* node helper_346_2_station_swap.mjs <묶음.mjs>
|
||||
* vite ssr 로 묶은 진짜 모듈(helper_346_2_swap_entry.ts) · 표준출력 JSON 한 벌.
|
||||
*/
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const store = () => {
|
||||
const m = new Map();
|
||||
return {
|
||||
getItem: (k) => (m.has(k) ? m.get(k) : null),
|
||||
setItem: (k, v) => m.set(k, String(v)),
|
||||
removeItem: (k) => m.delete(k),
|
||||
key: (i) => [...m.keys()][i] ?? null,
|
||||
get length() {
|
||||
return m.size;
|
||||
},
|
||||
clear: () => m.clear(),
|
||||
};
|
||||
};
|
||||
globalThis.window = globalThis;
|
||||
globalThis.sessionStorage = store();
|
||||
globalThis.localStorage = store();
|
||||
const any = new Proxy(function () {}, {
|
||||
get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : any),
|
||||
apply: () => any,
|
||||
construct: () => any,
|
||||
});
|
||||
globalThis.document = any;
|
||||
globalThis.requestAnimationFrame = (run) => setTimeout(run, 0);
|
||||
const toasts = [];
|
||||
globalThis.__toasts = toasts;
|
||||
|
||||
const m = await import(pathToFileURL(process.argv[2]).href);
|
||||
const P = "p346";
|
||||
const R = 9;
|
||||
const out = {};
|
||||
|
||||
/* 옛 목록 7 — 시점 · 격자 넷 · 관 하나 · 종점(간격 20 m) */
|
||||
const section = (chainage_m, kind, extra = {}) => ({
|
||||
station_id: `station_${String(Math.round(chainage_m * 1000)).padStart(12, "0")}`,
|
||||
chainage_m,
|
||||
label: "",
|
||||
kind,
|
||||
center_z: 100,
|
||||
azimuth_deg: 90,
|
||||
center_x: chainage_m,
|
||||
center_y: 0,
|
||||
frame: { left_xy: [0, 1] },
|
||||
samples: [{ offset_m: 0, elevation_m: 100, valid: true }],
|
||||
design: { design_elevation_m: 101, mark: `old-${chainage_m}` },
|
||||
...extra,
|
||||
});
|
||||
const oldDetail = () => ({
|
||||
longitudinal: {
|
||||
length_m: 100,
|
||||
stations: [],
|
||||
profile_alignment: { policy: { station_interval_m: 20 } },
|
||||
},
|
||||
cross_sections: [
|
||||
section(0, "bp"),
|
||||
section(20, "regular"),
|
||||
section(40, "regular"),
|
||||
section(45.054, "irregular", { structure: "파형강관 D800", culvert: {} }),
|
||||
section(60, "regular"),
|
||||
section(80, "regular"),
|
||||
section(100, "ep"),
|
||||
],
|
||||
});
|
||||
const OLD_KEYS = ["end:BP", "grid:1", "grid:2", "pipe:pipe-s1", "grid:3", "grid:4", "end:EP"];
|
||||
|
||||
/* 새 목록 8 — 셋 늘고(st-1 · st-2 · st-3) 둘 빠짐(grid:2 · grid:4) · 관 47 m 로 · 종점 90 m 로 */
|
||||
const station = (key, chainageM, kind, id = null, dy = 0) => ({
|
||||
key,
|
||||
chainageM,
|
||||
x: chainageM,
|
||||
y: dy,
|
||||
azimuthDeg: 90,
|
||||
left: [0, 1],
|
||||
kind,
|
||||
id,
|
||||
ground: {
|
||||
centerZ: 99,
|
||||
left: [{ offsetM: 0.5, z: 99.1 }],
|
||||
right: [{ offsetM: 0.5, z: 98.9 }],
|
||||
},
|
||||
planRadiusM: null,
|
||||
curveOuterSide: null,
|
||||
curveWideningM: null,
|
||||
});
|
||||
const NEW = (dy) => [
|
||||
station("end:BP", 0, "end", null, dy),
|
||||
station("grid:1", 20, "grid", null, dy),
|
||||
station("pipe:pipe-s1", 47, "pipe", "pipe-s1", dy),
|
||||
station("structure:st-1", 52, "structure", "st-1", dy),
|
||||
station("grid:3", 60, "grid", null, dy),
|
||||
station("structure:st-2", 65, "structure", "st-2", dy),
|
||||
station("structure:st-3", 75, "structure", "st-3", dy),
|
||||
station("end:EP", 90, "end", null, dy),
|
||||
];
|
||||
|
||||
/* 1. 순수 갈아끼움 */
|
||||
{
|
||||
const before = oldDetail();
|
||||
const swap = m.swapCrossSections(before, NEW(0), OLD_KEYS, 20);
|
||||
out.pure = {
|
||||
count: swap.detail.cross_sections.length,
|
||||
longCount: swap.detail.longitudinal.stations.length,
|
||||
added: swap.added,
|
||||
removed: swap.removed,
|
||||
moves: Object.fromEntries(swap.moves),
|
||||
moved: swap.moved,
|
||||
keptMark: swap.detail.cross_sections.map((s) => s.design?.mark ?? null),
|
||||
kinds: swap.detail.cross_sections.map((s) => s.kind),
|
||||
labels: [swap.detail.cross_sections[2].label, swap.detail.cross_sections[7].label],
|
||||
samples: swap.detail.cross_sections[1].samples.map((s) => s.offset_m),
|
||||
oldUntouched:
|
||||
before.cross_sections.length === 7 && before.cross_sections[3].chainage_m === 45.054,
|
||||
length: swap.detail.longitudinal.length_m,
|
||||
same: m.sameStationList(before.cross_sections, OLD_KEYS, NEW(0)),
|
||||
sameSelf: m.sameStationList(
|
||||
swap.detail.cross_sections,
|
||||
NEW(0).map((s) => s.key),
|
||||
NEW(0),
|
||||
),
|
||||
};
|
||||
out.format = [0, 45.054, 1234.5].map((c) => m.formatStation(c));
|
||||
}
|
||||
|
||||
/* 2. 가게 → 상세 갈아끼움 · 열쇠 이어 받기 · 노선 밖 고침 */
|
||||
{
|
||||
m.writeState("section-detail", oldDetail(), P, R);
|
||||
m.writeState("pipe-ids", { next: 2, ids: [{ id: "pipe-s1", at: 45.054 }] }, P);
|
||||
const first = await m.loadSectionDetail(P, R); // 세션 한 벌 → 캐시 · 측점 열쇠 세움
|
||||
const choice = (ground) => ({
|
||||
ground_type: ground,
|
||||
section_mode: "right_cut",
|
||||
ditch_side: "right",
|
||||
});
|
||||
m.writeCrossDesignChoice(P, R, 20, choice("soil"));
|
||||
m.writeCrossDesignChoice(P, R, 40, choice("soil"));
|
||||
m.writeCrossDesignChoice(P, R, 45.054, choice("blasting_rock"));
|
||||
const widths = m.createSessionMap(
|
||||
() => "aislo:test:crossw",
|
||||
(v) => v,
|
||||
);
|
||||
widths.values.set("45.05", 12);
|
||||
widths.values.set("45.05:outlet", 3);
|
||||
widths.values.set("20.00", 8);
|
||||
widths.persist(); // 조정창은 바꿀 때마다 세션에 씀(제어기와 같은 차례)
|
||||
const heard = [];
|
||||
// 관 · 구조물 자리 장부 — 새 목록에서 관은 47 m · 구조물 id 는 측점 계획이 줌
|
||||
m.writeState("pipe-ids", { next: 2, ids: [{ id: "pipe-s1", at: 47 }] }, P);
|
||||
m.useStructureIds(P, [
|
||||
{ structure_id: "st-1", chainage_m: 52 },
|
||||
{ structure_id: "st-2", chainage_m: 65 },
|
||||
{ structure_id: "st-3", chainage_m: 75 },
|
||||
]);
|
||||
const stopListen = m.subscribeSectionDetail(P, R, (detail, moves) =>
|
||||
heard.push({ count: detail.cross_sections.length, moves: Object.fromEntries(moves) }),
|
||||
);
|
||||
let redraws = 0;
|
||||
const stop = m.followSwaps(
|
||||
P,
|
||||
() => R,
|
||||
{ visible: () => true },
|
||||
async () => {
|
||||
redraws += 1;
|
||||
},
|
||||
);
|
||||
m.useDesignStore(P, null).set("stations", NEW(0.5)); // 좌표가 움직임 = 노선 모양 바뀜
|
||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
||||
const now = await m.loadSectionDetail(P, R);
|
||||
out.store = {
|
||||
replaced: now !== first,
|
||||
count: now.cross_sections.length,
|
||||
heard,
|
||||
choices: [...m.crossDesignChoices(P, R).entries()]
|
||||
.sort((a, b) => a[0] - b[0])
|
||||
.map(([at, c]) => [at, c.ground_type, c.section_mode ?? null]),
|
||||
rawKeys: Object.keys(m.readState("crossdesign", P, R) ?? {})
|
||||
.filter((k) => k !== "@at")
|
||||
.sort(),
|
||||
widths: Object.fromEntries(widths.values),
|
||||
redraws,
|
||||
};
|
||||
// 같은 목록을 다시 내면 갈아끼우지 않음
|
||||
const again = await m.swapSectionStations(P, R, NEW(0.5));
|
||||
out.store.again = again === null;
|
||||
stop();
|
||||
stopListen();
|
||||
}
|
||||
|
||||
/* 3. 보이는 카드 먼저 — 바로 그릴 수 */
|
||||
out.eager = [
|
||||
m.eagerCardCount({ clientHeight: 600, scrollTop: 0 }, 3, 250, 12),
|
||||
m.eagerCardCount({ clientHeight: 600, scrollTop: 1310 }, 3, 250, 12),
|
||||
m.eagerCardCount({ clientHeight: 0, scrollTop: 0 }, 2, 250, 12),
|
||||
];
|
||||
out.rename = Object.fromEntries(
|
||||
m.renameChainageKeys(
|
||||
new Map([
|
||||
["45.05", 1],
|
||||
["b45.05", 2],
|
||||
["45.05:outlet", 3],
|
||||
["60.00", 4],
|
||||
]),
|
||||
new Map([["45.05", "47.00"]]),
|
||||
),
|
||||
);
|
||||
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -0,0 +1,25 @@
|
||||
/* 346-2 4파 시험 묶음 진입점 — 측점 수가 바뀔 때 횡단 목록 갈아끼움(진짜 모듈 · vite ssr). */
|
||||
export {
|
||||
formatStation,
|
||||
renameChainageKeys,
|
||||
sameStationList,
|
||||
swapCrossSections,
|
||||
} from "../../B06_Section/logic/B06_Section_Station_Swap";
|
||||
export { eagerCardCount } from "../../B06_Section/B06_Section_UI_Section_View_Lazy";
|
||||
export {
|
||||
loadSectionDetail,
|
||||
subscribeSectionDetail,
|
||||
swapSectionStations,
|
||||
} from "../../B06_Section/B06_Section_Section_Store";
|
||||
export { followSwaps } from "../../B06_Section/B06_Section_UI_Page_Station_Swap";
|
||||
export {
|
||||
crossDesignChoices,
|
||||
writeCrossDesignChoice,
|
||||
} from "../../B06_Section/B06_Section_Cross_Design_Session";
|
||||
export { useStructureIds } from "../../B06_Section/B06_Section_Station_Keys";
|
||||
export {
|
||||
createSessionMap,
|
||||
moveSessionMaps,
|
||||
} from "../../B06_Section/B06_Section_UI_Page_Link_Session";
|
||||
export { useDesignStore } from "../../A00_Common/design/design_store";
|
||||
export { readState, writeState } from "../../A00_Common/b_page_state";
|
||||
@@ -0,0 +1,121 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""346-2 4파 — 측점 수가 바뀔 때 횡단 목록 갈아끼움.
|
||||
|
||||
못박는 것
|
||||
- 측점 셋 늘고 둘 빠진 목록 → 새 상세 객체(카드 8 · 종단 측점 8) · 이어 받은 측점은 설계 그대로 ·
|
||||
새 측점은 지반 표본 + 기본 설계 · 옛 상세는 그대로 · 같은 목록이면 안 갈아끼움.
|
||||
- 가게 `stations` → 캐시 상세 갈아끼움 · 듣는 쪽에 새 상세 + 누가거리 옮김 ·
|
||||
카드 선택은 측점 열쇠로 새 자리(관 45.054 → 47 m) · 노선 모양이 바뀌면 넘김 규칙
|
||||
(단면 유형 버림) · 사라진 측점 고침 버림 · 조정창 값도 옮긴 자리로.
|
||||
- 보이는 카드 먼저 — 보이는 줄 + 한 줄만 바로.
|
||||
- `formatStation` ↔ 파이썬 `format_station` 같은 글.
|
||||
|
||||
Node 헬퍼 `helper_346_2_station_swap.mjs` 가 vite ssr 로 묶은 진짜 모듈
|
||||
(`helper_346_2_swap_entry.ts`)을 부름.
|
||||
"""
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_Sections_Core import format_station # noqa: E402
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out(tmp_path_factory):
|
||||
work = tmp_path_factory.mktemp("b346")
|
||||
build = subprocess.run(
|
||||
[
|
||||
"node",
|
||||
str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"),
|
||||
"build",
|
||||
"--configLoader",
|
||||
"runner",
|
||||
"--ssr",
|
||||
"../resources/tester/helper_346_2_swap_entry.ts",
|
||||
"--outDir",
|
||||
str(work),
|
||||
"--logLevel",
|
||||
"error",
|
||||
],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=300,
|
||||
)
|
||||
assert build.returncode == 0, build.stderr
|
||||
bundle = work / "bundle.mjs"
|
||||
shutil.copy(work / "helper_346_2_swap_entry.js", bundle)
|
||||
proc = subprocess.run(
|
||||
["node", str(HERE / "helper_346_2_station_swap.mjs"), str(bundle)],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=120,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_셋_늘고_둘_빠진_목록으로_갈아끼움(out):
|
||||
pure = out["pure"]
|
||||
assert pure["count"] == 8 and pure["longCount"] == 8
|
||||
assert pure["added"] == ["structure:st-1", "structure:st-2", "structure:st-3"]
|
||||
assert pure["removed"] == ["grid:2", "grid:4"]
|
||||
assert pure["moves"] == {"45.05": "47.00", "100.00": "90.00"}
|
||||
assert pure["keptMark"] == [
|
||||
"old-0",
|
||||
"old-20",
|
||||
"old-45.054",
|
||||
None,
|
||||
"old-60",
|
||||
None,
|
||||
None,
|
||||
"old-100",
|
||||
]
|
||||
assert pure["kinds"] == [
|
||||
"bp",
|
||||
"regular",
|
||||
"irregular",
|
||||
"irregular",
|
||||
"regular",
|
||||
"irregular",
|
||||
"irregular",
|
||||
"ep",
|
||||
]
|
||||
assert pure["labels"] == ["STA.0+047.000", "STA.0+090.000"]
|
||||
assert pure["samples"] == [-0.5, 0, 0.5]
|
||||
assert pure["oldUntouched"] is True and pure["length"] == 90
|
||||
assert pure["same"] is False and pure["sameSelf"] is True
|
||||
|
||||
|
||||
def test_측점_글_파이썬과_같음(out):
|
||||
assert out["format"] == [format_station(c) for c in (0, 45.054, 1234.5)]
|
||||
|
||||
|
||||
def test_가게_측점으로_상세_갈아끼움_열쇠로_이어_받기(out):
|
||||
store = out["store"]
|
||||
assert store["replaced"] is True and store["count"] == 8
|
||||
assert store["heard"] == [{"count": 8, "moves": {"45.05": "47.00", "100.00": "90.00"}}]
|
||||
# 관 고침은 47 m 로 · 노선 모양이 바뀌어 단면 유형은 버림 · 40 m(grid:2) 고침은 노선 밖
|
||||
assert store["choices"] == [[20, "soil", None], [47, "blasting_rock", None]]
|
||||
assert store["rawKeys"] == ["grid:1", "pipe:pipe-s1"]
|
||||
assert store["widths"] == {"47.00": 12, "47.00:outlet": 3, "20.00": 8}
|
||||
assert store["redraws"] == 1 # 보이는 횡단 보기 = 카드 · 그림 한 번 다시
|
||||
assert store["again"] is True # 같은 목록 = 안 갈아끼움
|
||||
|
||||
|
||||
def test_보이는_카드_먼저(out):
|
||||
assert out["eager"] == [12, 27, 4] # 3열 · 600 px · (250+12) px 줄 = 3 줄 + 1
|
||||
assert out["rename"] == {"47.00": 1, "b47.00": 2, "47.00:outlet": 3, "60.00": 4}
|
||||
Reference in New Issue
Block a user