feat(M02): 구조물 도면 상세 · 카드 미리보기에 도각 얹음 — 외곽 좌하단 (0,0) · 보여주기 층
- 서버 GET /api/m02/structure-frame — B07 시스템 양식 00_template_A1 을 frame_entities(fit=False)로 1:1 배치 · 외곽 좌하단 (0,0) 으로 옮김 · 잠금 층 m02-structure-frame「도각(보기)」(마스터 양식 json 그대로) - 화면 mountDrawingTemplate frame 갈래 — 실을 때 도각 엔티티 · 층 덧붙임 · getDoc 에서 그 층 통째로 뺌(구조물 엔티티 · 저장 본문에 안 섞임) - 층 없는 빈 구조물 문서는 기본 층을 채움 — 잠금 도각 층만 남아 새 그림이 도각 층에 떨어지는 것 막음 - 불러온 DXF 층(b08-frame)과 층 id 갈라 둠 - 시험 test_m02_router_drawing 도각 원점 · 층 · 원본 양식 불변 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4
This commit is contained in:
@@ -45,8 +45,10 @@ export interface DrawingTemplateOptions {
|
||||
preview?: boolean;
|
||||
/** 좌표 0,0 기준점 십자를 그린다 — 구조물 도면(편집 · 미리보기)만. */
|
||||
originMarker?: boolean;
|
||||
/** 구조물 도면 — 도각이 없으므로 작도 영역 단추 · 자리표(도각 자리) 표시를 없애고, 문서의 작도 영역 값은 무시. */
|
||||
/** 구조물 도면 — 작도 영역 단추 · 자리표(도각 자리) 표시를 없애고, 문서의 작도 영역 값은 무시. */
|
||||
noArea?: boolean;
|
||||
/** B07 도각을 외곽 좌하단 (0,0) 에 얹음 — 잠금 층 · 보여주기만(돌려줄 때 뺌 · 저장 안 됨). */
|
||||
frame?: boolean;
|
||||
/** 파일 불러오기(📂) 단추를 안 둠 — 집계표 카드 미리보기(불러오기는 도면 상세에서만). */
|
||||
noImport?: boolean;
|
||||
}
|
||||
@@ -89,6 +91,36 @@ export interface DrawingTemplateHandle {
|
||||
const DEFAULT_AREA: [number, number, number, number] = [42, 47, 812, 567];
|
||||
const AREA_LABELS = ["왼쪽 x", "아래 y", "오른쪽 x", "위 y"];
|
||||
|
||||
/** 얹은 도각 층 — 서버 `STRUCTURE_FRAME_LAYER` 와 같음 · 돌려줄 때 이 층을 통째로 뺌 */
|
||||
const FRAME_LAYER_ID = "m02-structure-frame";
|
||||
interface StructureFrame {
|
||||
entities: Record<string, unknown>[];
|
||||
layer: Record<string, unknown> | null;
|
||||
}
|
||||
const BLANK_LAYER = { id: "기본", name: "기본", isVisible: true, isLocked: false };
|
||||
let framePromise: Promise<StructureFrame | null> | null = null;
|
||||
|
||||
/** 구조물 도각 — 한 번만 받아 돌려씀 · 못 받으면 도각 없이 */
|
||||
function fetchFrame(): Promise<StructureFrame | null> {
|
||||
framePromise ??= fetch(`${API_BASE_URL}/m02/structure-frame`, { credentials: "include" })
|
||||
.then((response) => (response.ok ? (response.json() as Promise<StructureFrame>) : null))
|
||||
.catch(() => null)
|
||||
.then((frame) => {
|
||||
if (!frame?.layer) framePromise = null; // 다음에 다시 받음
|
||||
return frame?.layer ? frame : null;
|
||||
});
|
||||
return framePromise;
|
||||
}
|
||||
|
||||
/** 도각 층을 뺀 도면 — 구조물 엔티티 · 층만 남음 */
|
||||
function withoutFrame(drawing: DrawingTemplateDoc): DrawingTemplateDoc {
|
||||
return {
|
||||
...drawing,
|
||||
entities: drawing.entities.filter((e) => e.layerId !== FRAME_LAYER_ID),
|
||||
layers: drawing.layers?.filter((layer) => layer.id !== FRAME_LAYER_ID),
|
||||
};
|
||||
}
|
||||
|
||||
async function importDrawingFile(
|
||||
file: File,
|
||||
): Promise<{ drawing: DrawingTemplateDoc; entity_count: number }> {
|
||||
@@ -202,10 +234,23 @@ export function mountDrawingTemplate(
|
||||
onChanged: (dirty) => options.onChanged?.(dirty),
|
||||
onHostSave: hostSave,
|
||||
});
|
||||
const load = (drawing: DrawingTemplateDoc): void => {
|
||||
let loadSeq = 0;
|
||||
const load = async (drawing: DrawingTemplateDoc): Promise<void> => {
|
||||
const mine = ++loadSeq;
|
||||
cad.beginLoading();
|
||||
const frame = options.frame ? await fetchFrame() : null;
|
||||
if (mine !== loadSeq) return; // 그새 다른 도면을 실음
|
||||
const own = withoutFrame(drawing);
|
||||
const shown = frame?.layer
|
||||
? {
|
||||
...own,
|
||||
entities: [...own.entities, ...frame.entities],
|
||||
// 층 없는 빈 문서 — 잠금 도각 층만 남으면 새 그림이 도각 층에 떨어짐 · 기본 층(서버 `_BLANK_LAYER`)을 채움
|
||||
layers: [...(own.layers?.length ? own.layers : [BLANK_LAYER]), frame.layer],
|
||||
}
|
||||
: drawing;
|
||||
cad.load(
|
||||
drawing,
|
||||
shown,
|
||||
null,
|
||||
!readOnly && !noArea,
|
||||
{},
|
||||
@@ -238,7 +283,7 @@ export function mountDrawingTemplate(
|
||||
importDrawingFile(file)
|
||||
.then((response) => {
|
||||
// 불러온 도각은 아직 저장하지 않는다 — 자리표를 놓고 [저장]을 눌러야 양식이 된다.
|
||||
load(response.drawing);
|
||||
void load(response.drawing);
|
||||
options.onChanged?.(true); // 불러온 것은 아직 저장 전 — 떠날 때 묻게 함
|
||||
showToast(`도형 ${response.entity_count}개를 불러왔습니다.`, "success");
|
||||
})
|
||||
@@ -260,7 +305,8 @@ export function mountDrawingTemplate(
|
||||
}
|
||||
|
||||
const getDoc = async (): Promise<DrawingTemplateDoc> => {
|
||||
const { drawing } = await cad.requestSave();
|
||||
const { drawing: saved } = await cad.requestSave();
|
||||
const drawing = options.frame ? withoutFrame(saved) : saved;
|
||||
if (noArea) {
|
||||
base = { ...base, ...drawing };
|
||||
return base;
|
||||
@@ -279,7 +325,7 @@ export function mountDrawingTemplate(
|
||||
root.append(cad.element);
|
||||
} else root.append(...(noArea && !canImport ? [] : [toolbar]), cad.element);
|
||||
container.append(root);
|
||||
load(doc);
|
||||
void load(doc);
|
||||
|
||||
return {
|
||||
getDoc,
|
||||
|
||||
@@ -80,7 +80,8 @@ function mountStructure(
|
||||
panelsCollapsed: true,
|
||||
preview: !edit, // 보기만 = 왼쪽 끌기 화면 옮기기 · 고치기 = 누름 고르기 · 단축키
|
||||
originMarker: true, // 좌표 0,0 기준점 십자
|
||||
noArea: true, // 구조물도는 도각이 없음
|
||||
noArea: true, // 작도 영역 칸 없음
|
||||
frame: true, // 도각 = 보여주기만 · 외곽 좌하단 (0,0)(56장)
|
||||
noImport: true, // 파일 불러오기는 도면 상세에서만(47-2)
|
||||
...hooks,
|
||||
});
|
||||
|
||||
@@ -14,7 +14,10 @@ from fastapi.responses import JSONResponse
|
||||
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Frame_Import import import_frame_file
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Template import (
|
||||
entities_bbox,
|
||||
frame_document,
|
||||
frame_entities,
|
||||
usable_bbox,
|
||||
validate_template_entities,
|
||||
)
|
||||
from B07_DesignDetail.B07_DesignDetail_Router import title_block_fields
|
||||
@@ -88,6 +91,45 @@ async def import_drawing_file(file: UploadFile = File(...)) -> JSONResponse:
|
||||
)
|
||||
|
||||
|
||||
# 얹은 도각 층 — 화면이 돌려줄 때 이 층을 통째로 뺌 · 불러온 DXF 층(`b08-frame`)과 갈라 둠
|
||||
STRUCTURE_FRAME_LAYER = "m02-structure-frame"
|
||||
|
||||
|
||||
def _on_frame_layer(entity: dict) -> dict:
|
||||
children = entity.get("children")
|
||||
return {
|
||||
**entity,
|
||||
"layerId": STRUCTURE_FRAME_LAYER,
|
||||
**({"children": [_on_frame_layer(c) for c in children]} if children else {}),
|
||||
}
|
||||
|
||||
|
||||
def structure_frame() -> dict:
|
||||
"""구조물 도면에 얹는 도각 — B07 시스템 양식 1:1 · 외곽 좌하단 (0,0) · 보여주기 층."""
|
||||
placed = frame_entities("m02-structure", usable_bbox(), fit=False)
|
||||
box = entities_bbox(placed)
|
||||
if not box:
|
||||
return {"entities": [], "layer": None}
|
||||
ox, oy = box[0], box[1]
|
||||
x0, y0, x1, y1 = usable_bbox()
|
||||
entities = frame_entities("m02-structure", (x0 - ox, y0 - oy, x1 - ox, y1 - oy), fit=False)
|
||||
return {
|
||||
"entities": [_on_frame_layer(entity) for entity in entities],
|
||||
"layer": {
|
||||
"id": STRUCTURE_FRAME_LAYER,
|
||||
"name": "도각(보기)",
|
||||
"isVisible": True,
|
||||
"isLocked": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get("/structure-frame")
|
||||
async def get_structure_frame() -> JSONResponse:
|
||||
"""구조물 도면 상세 · 카드 미리보기가 얹는 도각(56장)."""
|
||||
return JSONResponse({"status": "success", **structure_frame()})
|
||||
|
||||
|
||||
@router.get("/drawing-fields")
|
||||
async def get_drawing_fields(project_id: UUID | None = None) -> JSONResponse:
|
||||
"""자리표 키 목록. project_id 를 주면 그 프로젝트의 표제란 값도 함께(미리보기용)."""
|
||||
|
||||
@@ -433,6 +433,7 @@ export function createMain(
|
||||
headerSlot: slot,
|
||||
originMarker: at.kind === "structure",
|
||||
noArea: at.kind === "structure",
|
||||
frame: at.kind === "structure", // 도각 = 보여주기만(56장)
|
||||
});
|
||||
if (at.kind === "structure") toDrawing.onDrawing(editor, at, doc as StructureDoc);
|
||||
return;
|
||||
|
||||
@@ -65,3 +65,18 @@ def test_import_garbage_is_400_not_500():
|
||||
)
|
||||
assert response.status_code == 400, (data, response.text)
|
||||
assert response.json()["message"]
|
||||
|
||||
|
||||
def test_structure_frame_origin_at_zero():
|
||||
# 56장 — 구조물 도면에 얹는 도각: 외곽 좌하단 (0,0) · 잠금 도각 층 · 원본 양식은 그대로
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Template import entities_bbox, template_entities
|
||||
|
||||
before = entities_bbox(template_entities())
|
||||
body = client.get("/api/m02/structure-frame").json()
|
||||
x0, y0, x1, y1 = entities_bbox(body["entities"])
|
||||
assert abs(x0) < 1e-9 and abs(y0) < 1e-9
|
||||
assert abs((x1 - x0) - (before[2] - before[0])) < 1e-6
|
||||
assert body["layer"]["isLocked"] is True
|
||||
assert {e["layerId"] for e in body["entities"]} == {"m02-structure-frame"}
|
||||
assert body["layer"]["id"] == "m02-structure-frame" # 불러온 DXF 층(b08-frame)과 갈림
|
||||
assert entities_bbox(template_entities()) == before
|
||||
|
||||
Reference in New Issue
Block a user