Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1

This commit is contained in:
2026-10-10 11:55:38 +09:00
15 changed files with 423 additions and 34 deletions
@@ -162,4 +162,30 @@ out.notSheet = {
values: inputs().map((n) => n.value),
};
// 180-2 표 배율 칸 — 배치 칸 줄 뒤 · 0.5~2.0 · step 0.1 · [배율 적용]
const scales = [];
const toastStart = toasts.length;
slot.replaceChildren(
buildCrossLayoutControls([4, 4], null, () => {}, {
scale: { value: 1, onApply: (value) => scales.push(value) },
}),
);
const scaleInput = inputs()[2];
out.scale = {
labels: byClass(slot, "ui-field__label").map((n) => n.textContent),
input: {
value: scaleInput.value,
min: scaleInput.attrs.min,
max: scaleInput.attrs.max,
step: scaleInput.attrs.step,
},
buttons: buttons().map((n) => n.textContent),
};
scaleInput.value = "1.5";
buttons()[2].click();
scaleInput.value = "2.5";
buttons()[2].click();
out.scale.applied = scales;
out.scale.toasts = toasts.slice(toastStart).map((t) => t[0]);
process.stdout.write(JSON.stringify(out));
+5 -1
View File
@@ -119,7 +119,11 @@ def _project(tmp_path: Path, count: int) -> tuple[Path, Path]:
def test_layout_store_and_release_changed_confirmed(tmp_path: Path) -> None:
root, longitudinal_path = _project(tmp_path, 20)
assert read_cross_layout(root) == {"default": [4, 4], "sheets": {}} # 파일 없으면 기본
assert read_cross_layout(root) == {
"default": [4, 4],
"sheets": {},
"table_scale": 1.0,
} # 파일 없으면 기본
ids = [s["id"] for s in _cross_sheet_plan(root, longitudinal_path)]
assert ids == ["cross_s00000m", "cross_s00320m"] # 16 + 4
manifest = _read_manifest(root)
@@ -61,3 +61,12 @@ def test_장_아니면_이_장_꺼짐(out):
assert not_sheet["disabled"] == [True, False]
assert not_sheet["defaults"] == [[4, 4]]
assert not_sheet["values"] == ["4", "4"]
def test_표_배율_칸(out):
scale = out["scale"]
assert scale["labels"] == ["배치 열", "행", "표 배율"]
assert scale["input"] == {"value": "1", "min": "0.5", "max": "2", "step": "0.1"}
assert scale["buttons"] == ["이 장", "모든 장", "배율 적용"]
assert scale["applied"] == [1.5]
assert scale["toasts"] == ["error"] # 2.5 = 범위 밖
@@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
"""180-2 B07 횡단 장 — 표 · 표 위 글 배율(모든 장 한 값 · 0.5~2.0 · 기본 1).
못박는 것
- 배율 1.5 = 표 폭 162 · 높이 60 · 글 줄 높이 × 1.5 · 표 아래선은 칸 아래 + 여백 그대로.
- 그림 아래 끝 = 글(없으면 표) 위 8 mm(간격은 배율 안 탐) · 넓은 표가 칸을 넘으면 overflow_cells.
- 배율 1 = 배율 칸 없는 배치와 같은 엔티티.
- cross_layout.json 이 배율을 지킴 · 범위 밖 = 거절 · 배율만 바꾸면 확정 장 안 풂.
- 지금과 다른 배율로 확정된 장 = 그대로 + stale_scale 안내.
"""
import json
from pathlib import Path
import pytest
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import DRAWING_FORMAT, GROUND_LAYER_ID
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Sheet import (
CELL_TABLE_PAD_MM,
build_cross_sheet,
plan_cross_sheets,
)
from B07_DesignDetail.B07_DesignDetail_Engine_Template import (
entities_bbox,
usable_area,
use_project_templates,
)
from B07_DesignDetail.B07_DesignDetail_Router_Support import (
CROSS_SHEET_LAYOUT,
_cross_sheet_plan,
_read_drawing,
_read_manifest,
_write_manifest,
read_cross_layout,
write_cross_layout,
)
NOTE = {"structure_note": "L형 측구\n집수정 보강"}
def _source(chainage: float, lo: int = -5, hi: int = 5) -> dict:
return {
"chainage_m": chainage,
"samples": [
{"valid": True, "offset_m": float(x), "elevation_m": 100.0 + 0.05 * x}
for x in range(lo, hi + 1)
],
}
def _drawing(layout: dict, designs: list[dict | None]) -> tuple[dict, dict]:
use_project_templates(None)
sources = [_source(i * 20.0) for i in range(len(designs))]
sheet = plan_cross_sheets([s["chainage_m"] for s in sources], layout)[0]
sections = [
{
"chainage": s["chainage_m"],
"source": s,
"design": design,
"quantity_table": {"ground": 1.0, "planned": 0.5},
"title": "No.0",
}
for s, design in zip(sources, designs, strict=True)
]
return sheet, build_cross_sheet(sheet, sections)
def _tables(drawing: dict) -> list[dict]:
return [e for e in drawing["entities"] if e["type"] == "Table"]
def test_배율_15_표_크기와_쌓기() -> None:
sheet, drawing = _drawing({"default": [2, 2], "table_scale": 1.5}, [NOTE, None])
assert sheet["table_scale"] == 1.5
_w, h = usable_area()
tables = _tables(drawing)
for table, slot in zip(tables, sheet["slots"], strict=True):
shape = table["shapeData"]
assert sum(shape["columnWidths"]) == pytest.approx(162.0)
assert sum(shape["rowHeights"]) == pytest.approx(60.0)
bottom = shape["origin"]["y"] - sum(shape["rowHeights"])
assert bottom == pytest.approx(slot[1] - h / 4 + CELL_TABLE_PAD_MM) # 표 아래선 그대로
grounds = [
entities_bbox([e]) for e in drawing["entities"] if e.get("layerId") == GROUND_LAYER_ID
]
# 글 2 줄 × 3.0 × 1.5 = 9 mm · 간격 8 mm 는 배율 안 탐
tops = [t["shapeData"]["origin"]["y"] for t in tables]
assert grounds[0][1] == pytest.approx(tops[0] + 9.0 + 8.0)
assert grounds[1][1] == pytest.approx(tops[1] + 8.0)
def test_배율_1_은_지금과_같음() -> None:
_s, plain = _drawing({"default": [2, 2]}, [NOTE, None])
_s, one = _drawing({"default": [2, 2], "table_scale": 1.0}, [NOTE, None])
assert one["entities"] == plain["entities"]
assert sum(_tables(one)[0]["shapeData"]["columnWidths"]) == pytest.approx(108.0)
def test_넓은_표_넘침() -> None:
w, _h = usable_area()
# 4 × 4 칸 폭 192.5 — 표 108 × 2.0 = 216 이 칸을 넘음
_s, drawing = _drawing({"default": [4, 4], "table_scale": 2.0}, [None])
assert w / 4 < 216.0
assert len(drawing["overflow_cells"]) == 1
_s, drawing = _drawing({"default": [4, 4], "table_scale": 1.0}, [None])
assert drawing["overflow_cells"] == []
def _project(tmp_path: Path, count: int) -> tuple[Path, Path]:
root = tmp_path / "project"
cross_dir = root / "B06_Section" / "cross_sections"
cross_dir.mkdir(parents=True)
longitudinal_path = root / "B06_Section" / "longitudinal" / "longitudinal.json"
longitudinal_path.parent.mkdir(parents=True)
stations = []
for i in range(count):
chainage = i * 20
stations.append({"chainage_m": float(chainage), "center_x": 0.0, "center_y": 0.0})
(cross_dir / f"cross_{chainage:05d}m.json").write_text(
json.dumps(_source(float(chainage))), "utf-8"
)
longitudinal_path.write_text(json.dumps({"stations": stations}), "utf-8")
return root, longitudinal_path
def test_배치_파일_배율_저장_범위(tmp_path: Path) -> None:
root, longitudinal_path = _project(tmp_path, 5)
ids = [s["id"] for s in _cross_sheet_plan(root, longitudinal_path)]
manifest = _read_manifest(root)
manifest["drawings"] = {i: {"confirmed": True} for i in ids}
_write_manifest(root, manifest)
# 배율만 바꿈 — 담긴 측점 그대로라 확정 장 안 풂
assert (
write_cross_layout(root, longitudinal_path, {"default": [4, 4], "table_scale": 1.3}) == []
)
assert read_cross_layout(root)["table_scale"] == 1.3
assert _read_manifest(root)["drawings"][ids[0]]["confirmed"] is True
assert _cross_sheet_plan(root, longitudinal_path)[0]["table_scale"] == 1.3
for bad in (0.4, 2.1, "1.2", True):
with pytest.raises(ValueError):
write_cross_layout(root, longitudinal_path, {"default": [4, 4], "table_scale": bad})
assert read_cross_layout(root)["table_scale"] == 1.3
def test_옛_배율_확정_장_안내(tmp_path: Path) -> None:
stage = tmp_path / "B07_DesignDetail"
(stage / "drawings").mkdir(parents=True)
saved = {"format": DRAWING_FORMAT, "entities": [], "layers": []}
(stage / "drawings" / "cross_s00000m.json").write_text(json.dumps(saved), encoding="utf-8")
entry = {"kind": "cross", "label": "1장", "confirmed": True, "layout": CROSS_SHEET_LAYOUT}
manifest = {"drawings": {"cross_s00000m": entry}}
(stage / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
def read() -> dict:
_k, _l, drawing, confirmed, _t = _read_drawing(
tmp_path, tmp_path / "x.json", "cross_s00000m"
)
assert confirmed
return drawing
assert "stale_scale" not in read() # 배율 적힘 없음 = 1 · 지금도 1
(stage / "cross_layout.json").write_text(
json.dumps({"default": [4, 4], "sheets": {}, "table_scale": 1.5}), encoding="utf-8"
)
assert read().get("stale_scale") is True
entry["table_scale"] = 1.5
(stage / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
assert "stale_scale" not in read()
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
"""180-1 B07 횡단 장 — 원지반선 고르기 · 측점 한 벌 groupId.
못박는 것
- 횡단 장 · 측점 한 장의 원지반 층은 안 잠김(종단은 그대로 잠김).
- 측점마다 groupId 하나(원지반 · 계획선 · 표 · 글 전부) · 측점끼리 다름.
- 칸 격자선 · 도각엔 groupId 없음.
- cross_placements 의 group_id = 그 측점 엔티티 값(화면 덧그림이 같은 묶음).
"""
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import (
FRAME_LAYER_ID,
GROUND_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
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 _design_line() -> list[dict]:
return [{"offset_m": x, "elevation_m": 100.0} for x in (-3.0, 0.0, 3.0)]
def _drawing() -> dict:
use_project_templates(None)
chainages = [0.0, 20.0, 40.0, 60.0]
sheet = plan_cross_sheets(chainages, {"default": [2, 2]})[0]
sections = [
{
"chainage": c,
"source": _source(c),
"design_line": _design_line(),
"design": {"structure_note": "L형 측구"},
"quantity_table": {"ground": 1.0, "planned": 0.5},
"title": f"No.{int(c // 20)}",
}
for c in chainages
]
return build_cross_sheet(sheet, sections)
def _ground_locked(drawing: dict) -> bool:
return next(layer for layer in drawing["layers"] if layer["id"] == GROUND_LAYER_ID)["isLocked"]
def test_원지반_층_안_잠김() -> None:
assert _ground_locked(_drawing()) is False
single = build_cross_drawing(_source(0.0), "cross_00000m", _design_line(), None, None, "")
assert _ground_locked(single) is False
def test_측점마다_groupId_하나() -> None:
drawing = _drawing()
placements = drawing["cross_placements"]
assert len(placements) == 4
group_ids = [p["group_id"] for p in placements]
assert len(set(group_ids)) == 4 # 측점끼리 다름
grouped = [e for e in drawing["entities"] if "groupId" in e]
assert {e["groupId"] for e in grouped} == set(group_ids)
for group_id in group_ids:
members = [e for e in grouped if e["groupId"] == group_id]
layers = {e["layerId"] for e in members}
types = {e["type"] for e in members}
assert GROUND_LAYER_ID in layers and "Table" in types and "Text" in types
# 격자선 · 도각은 묶지 않음
assert all("groupId" not in e for e in drawing["entities"] if e["layerId"] == FRAME_LAYER_ID)
assert all(e["layerId"] != FRAME_LAYER_ID for e in grouped)
def test_groupId_결정적() -> None:
first = [p["group_id"] for p in _drawing()["cross_placements"]]
assert first == [p["group_id"] for p in _drawing()["cross_placements"]]