refactor(b07): 횡단 구조물 얹기 몸통 = Cross_Overlay.placeStructureEntities · Node 가짜 DOM = Node_Dom 파일 (302-2 앞)

- 화면 appendStructureEntities = 정본 상세 읽기 + placeStructureEntities 부름 · 동작 그대로
- 가짜 문서에 addEventListener · removeEventListener 빈 함수 — 화면 모듈이 불러올 때 거는 문서 이벤트로 덧그림 범위 Node 번들이 시작부터 멈추던 흠 (10-04 무렵부터 서버 장 배치가 덧그림 범위 없이 섬)
- 시험 212 = 옮긴 자리에서 글 찾음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
This commit is contained in:
2026-10-13 08:01:33 +09:00
co-authored by Claude Opus 5.5
parent 5034fa4ea8
commit 8230788e96
5 changed files with 250 additions and 229 deletions
@@ -14,40 +14,9 @@
import type { CrossSection } from "../B06_Section/B06_Section_Api_Fetch";
import { loadSectionDetail } from "../B06_Section/B06_Section_Section_Store";
import {
DESIGN_LAYER_ID,
entityBox,
overlayEntities,
type Entity,
} from "./B07_DesignDetail_Cross_Overlay";
import { placeStructureEntities, type CrossPlacement } from "./B07_DesignDetail_Cross_Overlay";
/** 서버가 도면에 실어 보내는 측점별 실좌표(m) → 종이(mm) 변환값. */
export interface CrossPlacement {
chainage_m: number;
ox: number;
oy: number;
dy: number;
mm_per_m: number;
x0: number;
x1: number;
/** 블록 테두리(종이 mm) [x0, y0, x1, y1] — 그림을 이 안으로 자른다. */
frame?: number[];
/** 측점 한 벌 묶음(180-1) — 덧그림도 같은 묶음으로 함께 골라짐. */
group_id?: string;
/** 측점 도면층(212-2) — 덧그림도 그 층에. */
layer_id?: string;
/** 갈아 끼울 서버 설계선 id(212-2) — 측점 층이면 층으로는 못 가림. */
design_ids?: string[];
}
/** 개체(자식 선까지)를 측점 층으로(212-2). */
function moveToLayer(entity: Entity, layerId: string): void {
entity.layerId = layerId;
for (const child of (entity.children as Entity[] | undefined) ?? []) moveToLayer(child, layerId);
}
/** 측점과 도면 배치를 같은 자리로 볼 허용 오차(m). 정본이 누가거리를 0.01m로 끊어 쓴다. */
const CHAINAGE_TOLERANCE_M = 0.02;
export type { CrossPlacement };
/**
* 도면에 구조물 엔티티를 얹는다 (제자리 수정). 배치 메타가 없거나 자료를 못 읽으면
@@ -68,48 +37,6 @@ export async function appendStructureEntities(
} catch {
return 0;
}
let added = 0;
const fresh: Entity[] = [];
const replaced: number[][] = [];
const designIds = new Set<string>();
for (const placement of placements) {
const section = sections.find(
(item) => Math.abs(item.chainage_m - placement.chainage_m) <= CHAINAGE_TOLERANCE_M,
);
const frame = placement.frame;
if (!section || !frame) continue;
const x = (offset: number): number => offset * placement.mm_per_m + placement.ox;
const y = (elevation: number): number =>
-((elevation - placement.dy) * placement.mm_per_m + placement.oy);
let entities: Entity[];
try {
const drawn = overlayEntities(section, sections, x, y, frame);
if (!drawn) continue;
entities = drawn;
} catch {
continue; // 한 측점의 기하 실패가 도면 전체를 막으면 안 된다.
}
if (!entities.length) continue;
if (placement.group_id) for (const entity of entities) entity.groupId = placement.group_id;
if (placement.layer_id) for (const entity of entities) moveToLayer(entity, placement.layer_id);
for (const id of placement.design_ids ?? []) designIds.add(id);
// 이 블록 설계선은 우리가 다시 그렸다 — 서버가 낸 트림 전 설계선은 걷어낸다.
replaced.push(frame);
fresh.push(...entities);
added += entities.length;
}
if (replaced.length) {
// 걷어내기는 새 엔티티를 넣기 **전에** 한다 — 뒤에 하면 우리 것까지 같이 지운다.
drawing.entities = drawing.entities.filter((entity) => {
if (entity.layerId !== DESIGN_LAYER_ID && !designIds.has(entity.id as string)) return true;
const box = entityBox(entity);
if (!box) return true;
return !replaced.some(
([fx0, fy0, fx1, fy1]) => box[0] >= fx0 && box[2] <= fx1 && box[1] >= fy0 && box[3] <= fy1,
);
});
}
drawing.entities.push(...fresh);
return added;
// 얹는 몸통 = `_Cross_Overlay.placeStructureEntities`(인쇄 Node 302-2 와 한 벌)
return placeStructureEntities(sections, drawing);
}