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

This commit is contained in:
2026-10-10 08:48:48 +09:00
23 changed files with 810 additions and 78 deletions
@@ -12,9 +12,10 @@
import "./M02_MasterTemplete_Drawing.css";
import { API_BASE_URL } from "@config/config_frontend";
import { createCadHost, type CadHostDrawing } from "@ui/cad_host/cad_host";
import { createCadHost, type CadGuideArea, type CadHostDrawing } from "@ui/cad_host/cad_host";
import { createAreaFields, DEFAULT_DRAWING_AREA as DEFAULT_AREA } from "@ui/cad_host/cad_host_area";
import { createCadTabs, type CadTab, type CadTabs } from "@ui/cad_host/cad_host_tabs";
import { createButton, createInputField, showToast } from "@ui/ui_template_elements";
import { createButton, showToast } from "@ui/ui_template_elements";
import { openModal } from "@ui/ui_template_modal";
/** 도면 양식 문서 — 작도 영역은 [x0, y0, x1, y1] (양식 좌표 mm). */
@@ -105,15 +106,13 @@ export interface DrawingTemplateHandle {
destroy: () => void;
}
/** 작도 영역 칸이 없는 양식이 쓰는 값 — 서버 `Engine_Template._A1_INNER` 와 같다. */
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;
/** 작도 영역(도각과 같은 좌표) — CAD 가이드 선(169-4 · 보기만) */
area?: CadGuideArea | null;
}
const BLANK_LAYER = { id: "기본", name: "기본", isVisible: true, isLocked: false };
const framePromises = new Map<string, Promise<StructureFrame | null>>();
@@ -181,42 +180,31 @@ export function mountDrawingTemplate(
// 작도 영역 — 도면 내용이 이 칸 한가운데에 놓인다. 제목 줄에는 값이 다 보이는 단추 하나,
// 누르면 칸 넷이 작은 창으로 펼쳐진다(칸은 창을 닫아도 그대로 살아 있음).
const areaForm = document.createElement("div");
areaForm.className = "m02-drawing__areaform";
const start = doc.drawing_area ?? DEFAULT_AREA;
const areaInputs = AREA_LABELS.map((label, index) => {
const field = createInputField({ label, type: "number", value: String(start[index]) });
field.input.disabled = readOnly;
field.input.addEventListener("input", () => {
options.onChanged?.(true);
readArea();
});
return field;
});
areaForm.append(...areaInputs.map((field) => field.root));
// 칸 ↔ CAD 가이드 사각형 끌기가 한 벌(169-3 · 공용 칸 `cad_host_area`).
const areaButton = createButton({ label: "", variant: "ghost" });
areaButton.classList.add("m02-drawing__areabtn");
areaButton.title = "작도 영역 — 도면 내용이 놓이는 칸(양식 좌표 mm) · 눌러서 고침";
areaButton.title = "작도 영역 — 도면 내용이 놓이는 칸(양식 좌표 mm) · 눌러서 고침 · 점선 끌기";
let areaModal: { close: () => void } | null = null;
const readArea = (): [number, number, number, number] | null => {
const values = areaInputs.map((field) => Number(field.input.value));
const valid =
areaInputs.every((field) => field.input.value.trim() !== "") &&
values.every(Number.isFinite) &&
values[0] < values[2] &&
values[1] < values[3];
// 빈 칸 · 숫자 아님은 그 칸만 짚음 — 모두 숫자인데 어긋나면 크기 순서 안내
const own = areaInputs.map((field) => {
const raw = field.input.value.trim();
return raw === "" ? "값을 넣을 것" : Number.isFinite(Number(raw)) ? undefined : "숫자만";
});
const order = own.some(Boolean) ? undefined : "왼쪽<오른쪽 · 아래<위";
areaInputs.forEach((field, index) => field.setError(valid ? undefined : (own[index] ?? order)));
areaButton.textContent = `작도 영역 ${areaInputs.map((f) => f.input.value.trim() || "?").join(" · ")}`;
areaButton.classList.toggle("is-invalid", !valid);
return valid ? (values as [number, number, number, number]) : null;
const showArea = (area: CadGuideArea | null): void => {
areaButton.textContent = area
? `작도 영역 ${area.map((value) => Math.round(value * 10) / 10).join(" · ")}`
: "작도 영역 ?";
areaButton.classList.toggle("is-invalid", !area);
};
const areaFields = createAreaFields(doc.drawing_area ?? DEFAULT_AREA, {
readOnly,
onInput: (area) => {
options.onChanged?.(true);
showArea(area);
if (area) cad.setGuideArea(area);
},
});
const areaForm = areaFields.element;
areaForm.classList.add("m02-drawing__areaform");
const readArea = (): CadGuideArea | null => {
const area = areaFields.read();
showArea(area);
return area;
};
readArea();
areaButton.addEventListener("click", () => {
@@ -257,6 +245,11 @@ export function mountDrawingTemplate(
onChanged: (dirty) => options.onChanged?.(dirty),
onHostSave: hostSave,
onPlaceCancel: () => options.onPlaceCancel?.(),
onGuideChange: (area) => {
areaFields.set(area);
showArea(area);
options.onChanged?.(true);
},
});
let loadSeq = 0;
/** `after` = 싣기를 보낸 바로 뒤(이 싣기가 마지막일 때만) — 표 놓기를 「열린 뒤」로 미룰 때 */
@@ -288,6 +281,12 @@ export function mountDrawingTemplate(
panelsCollapsed: options.panelsCollapsed,
viewOnly: options.preview === true,
originMarker: options.originMarker === true,
// 가이드 선 — 양식은 작도 영역(끌기 · 169-3) · 구조물은 얹은 도각의 영역(보기만 · 169-4)
guide: frame?.area
? { area: frame.area }
: noArea
? null
: { area: readArea() ?? DEFAULT_AREA, editable: !readOnly },
},
);
after?.();
@@ -17,6 +17,7 @@ from B07_DesignDetail.B07_DesignDetail_Engine_Template import (
entities_bbox,
frame_document,
frame_entities,
frame_guide_area,
usable_bbox,
validate_template_entities,
)
@@ -119,6 +120,8 @@ def structure_frame(number: str | None = None) -> dict:
)
return {
"entities": [_on_frame_layer(entity) for entity in entities],
# 작도 영역(도각과 같은 좌표) — CAD 가이드 선(169-4 · 보기만)
"area": frame_guide_area(entities),
"layer": {
"id": STRUCTURE_FRAME_LAYER,
"name": "도각(보기)",