표지는 DRAWING_GROUPS에 kind 없이 라벨만 있어 화면에서 열 수 없었다. 실무 설계도면 원본(울진 소광리, A3) 1쪽의 잉크 bbox를 pymupdf로 mm 실측해 A1로 2배 환산했다. 원본에 도각이 없다 — 좌우 테두리 없이 상·하 굵은 가로선 두 줄뿐이라, 사용자가 고른 "도각 없는 전면 디자인"이 실무 그대로였다. - resources/template_2dDrawing/00_template_cover.json 신설. 엔티티 14개(재단 표식 Point 4·띠 Hatch 3·글자 Text 7), bbox가 정확히 A1(840x594). 좌표계는 00_template_A1 과 같아 도각 도면과 나란히 놓인다. - 굵은 띠는 solid Hatch로 냈다. lineWidth는 캔버스 화면 픽셀이라(screenCanvas .drawController.ts:261) 확대해도 두꺼워지지 않아 실치수를 못 낸다. - 글자 크기는 잉크 높이가 아니라 폭으로 잡았다. 원본은 장평이 좁은 CAD 글꼴이라 높이를 그대로 옮기면 위치값이 종이 밖으로 44mm 넘치고 라벨과 겹친다. - B07_DesignDetail_Engine_Cad_Cover.py 신설. frame_entities()를 쓰지 않는다 — _transform_entity()가 Hatch의 points 배열을 못 옮기고, 표지는 A1 실치수 고정이라 애초에 변환이 필요 없다. 공용 함수는 건드리지 않았다. - kind "cover" 배선: Schema Literal, Api_Fetch, UI_Page DRAWING_GROUPS, Router_Support 목록·빌드 분기·확정 캐시 매핑. 값(공사명·위치·사업량·시행청)은 아직 빈칸이다 — 메타 배선은 다음 판. 검증: tmp/tests/test_cover_template.py 6건 통과(A1 치수·Hatch 두께·잠금 레이어· 치환·lru_cache 오염 없음). 공용 브라우저에서 표지를 열어 엔티티 14개 전부 b08-frame, Ctrl+A 선택 0 확인. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
140 lines
4.2 KiB
TypeScript
140 lines
4.2 KiB
TypeScript
/* B07 상세 설계 도면 목록·단건 API 클라이언트. */
|
|
|
|
import { API_BASE_URL, API_TIMEOUT_MS } from "@config/config_frontend";
|
|
|
|
export interface DesignDrawingItem {
|
|
id: string;
|
|
kind: "cover" | "longitudinal" | "cross" | "mass_haul" | "watershed";
|
|
label: string;
|
|
chainage_m: number | null;
|
|
confirmed: boolean;
|
|
}
|
|
|
|
export interface CadDrawing {
|
|
entities: Record<string, unknown>[];
|
|
/** 횡단도 측점별 실좌표(m) → 종이(mm) 변환값. 구조물을 같은 자리에 얹는 데 쓴다. */
|
|
cross_placements?: {
|
|
chainage_m: number;
|
|
ox: number;
|
|
oy: number;
|
|
dy: number;
|
|
mm_per_m: number;
|
|
x0: number;
|
|
x1: number;
|
|
}[];
|
|
layers: {
|
|
id: string;
|
|
name: string;
|
|
isVisible: boolean;
|
|
isLocked: boolean;
|
|
}[];
|
|
}
|
|
|
|
export interface DesignDrawingListResponse {
|
|
status: string;
|
|
project_id: string;
|
|
route_id: number;
|
|
drawings: DesignDrawingItem[];
|
|
}
|
|
|
|
/** 수량 산출표 값 (미산정 항목은 null). 백엔드 `_quantity_table`의 키와 대응. */
|
|
export type QuantityTable = Record<string, number | null>;
|
|
|
|
/** 측구 형식별 규격 (B06 엔진 ditch_spec 신구조와 1:1). */
|
|
export type DitchSpec =
|
|
| { type: "none" }
|
|
| { type: "standard"; top_width_m: number; bottom_width_m: number; depth_m: number }
|
|
| { type: "l_type"; width_m: number; depth_m: number };
|
|
|
|
/** B06에서 지정한 설계(지반정보·계획정보). 횡단도에만 존재. status로 잠정/확정 구분. */
|
|
export interface CrossDesignInfo {
|
|
ground_type: "soil" | "ripping_rock" | "blasting_rock";
|
|
geometry_preset: "soil" | "rock";
|
|
section_mode: "left_cut" | "right_cut" | "both_cut" | "both_fill";
|
|
ditch_side: "left" | "right";
|
|
ditch_type?: "standard" | "l_type" | null;
|
|
ditch_enabled?: boolean;
|
|
cut_slope_ratio: number;
|
|
fill_slope_ratio: number;
|
|
roadbed_width_m: number;
|
|
carriageway_width_m?: number;
|
|
cross_slope_pct?: number;
|
|
paved?: boolean;
|
|
ditch: DitchSpec;
|
|
road_edges?: Record<"left" | "right", { offset_m: number; elevation_m: number }>;
|
|
design_elevation_m: number;
|
|
cut_area_m2: number;
|
|
fill_area_m2: number;
|
|
status?: "provisional" | "confirmed";
|
|
}
|
|
|
|
export interface DesignDrawingResponse {
|
|
status: string;
|
|
project_id: string;
|
|
route_id: number;
|
|
id: string;
|
|
kind: "cover" | "longitudinal" | "cross" | "mass_haul" | "watershed";
|
|
label: string;
|
|
drawing: CadDrawing;
|
|
confirmed: boolean;
|
|
quantity_table?: QuantityTable | null;
|
|
design?: CrossDesignInfo | null;
|
|
}
|
|
|
|
export interface DesignDrawingConfirmResponse {
|
|
status: string;
|
|
project_id: string;
|
|
id: string;
|
|
confirmed: boolean;
|
|
all_confirmed: boolean;
|
|
design?: CrossDesignInfo | null;
|
|
}
|
|
|
|
async function requestJson<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
const controller = new AbortController();
|
|
const timeoutId = window.setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}${path}`, {
|
|
...init,
|
|
credentials: "include",
|
|
headers: { "Content-Type": "application/json" },
|
|
signal: controller.signal,
|
|
});
|
|
const payload = (await response.json()) as T & { message?: string };
|
|
if (!response.ok) throw new Error(payload.message ?? `HTTP ${response.status}`);
|
|
return payload;
|
|
} finally {
|
|
window.clearTimeout(timeoutId);
|
|
}
|
|
}
|
|
|
|
export function fetchDesignDrawingList(projectId: string): Promise<DesignDrawingListResponse> {
|
|
return requestJson(`/projects/${projectId}/design-drawings`);
|
|
}
|
|
|
|
export function fetchDesignDrawing(
|
|
projectId: string,
|
|
drawingId: string,
|
|
): Promise<DesignDrawingResponse> {
|
|
return requestJson(`/projects/${projectId}/design-drawings/${encodeURIComponent(drawingId)}`);
|
|
}
|
|
|
|
export function confirmDesignDrawing(
|
|
projectId: string,
|
|
drawingId: string,
|
|
drawing: CadDrawing,
|
|
quantityTable?: QuantityTable | null,
|
|
): Promise<DesignDrawingConfirmResponse> {
|
|
return requestJson(
|
|
`/projects/${projectId}/design-drawings/${encodeURIComponent(drawingId)}/confirm`,
|
|
{ method: "PUT", body: JSON.stringify({ drawing, quantity_table: quantityTable ?? null }) },
|
|
);
|
|
}
|
|
|
|
export function invalidateDesignDrawing(projectId: string, drawingId: string): Promise<void> {
|
|
return requestJson(
|
|
`/projects/${projectId}/design-drawings/${encodeURIComponent(drawingId)}/invalidate`,
|
|
{ method: "POST" },
|
|
);
|
|
}
|