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:
@@ -14,163 +14,13 @@
|
||||
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import type { SectionDetailResponse } from "../B06_Section/B06_Section_Api_Fetch";
|
||||
import "./B07_DesignDetail_Node_Dom"; // 최소 DOM(globalThis.document) — 그리기 코드보다 먼저
|
||||
import { overlayEntities, type Entity } from "./B07_DesignDetail_Cross_Overlay";
|
||||
|
||||
/** 화면과 같은 종이 축척(1/100 → 1 m = 10 mm) — 공선점 허용 오차가 종이 mm 라 같아야 한다. */
|
||||
const MM_PER_M = 10;
|
||||
const OPEN_FRAME = [-1e12, -1e12, 1e12, 1e12];
|
||||
|
||||
/* ── 최소 DOM: 그리기 코드가 쓰는 만큼만 ─────────────────────────────────── */
|
||||
|
||||
type Child = FakeElement | string;
|
||||
|
||||
/** 선택자 하나(`tag`, `tag[attr]`)가 이 요소에 맞나 — 그리기·수확이 쓰는 꼴만 안다. */
|
||||
function matches(element: FakeElement, selectors: string): boolean {
|
||||
return selectors.split(",").some((raw) => {
|
||||
const found = /^([\w-]*)(?:\[([\w-]+)\])?$/.exec(raw.trim());
|
||||
if (!found) return false;
|
||||
const [, tag, attribute] = found;
|
||||
if (tag && element.tagName !== tag) return false;
|
||||
return !attribute || element.attributes.has(attribute);
|
||||
});
|
||||
}
|
||||
|
||||
class FakeElement {
|
||||
readonly attributes = new Map<string, string>();
|
||||
readonly childNodes: Child[] = [];
|
||||
parentNode: FakeElement | null = null;
|
||||
readonly dataset: Record<string, string> = {};
|
||||
readonly style: Record<string, unknown> = { setProperty: () => undefined };
|
||||
readonly classList = {
|
||||
add: (...names: string[]) => this.setClasses([...this.classes(), ...names]),
|
||||
remove: (...names: string[]) =>
|
||||
this.setClasses(this.classes().filter((name) => !names.includes(name))),
|
||||
toggle: (name: string, force?: boolean) => {
|
||||
const on = force ?? !this.classes().includes(name);
|
||||
this.setClasses(on ? [...this.classes(), name] : this.classes().filter((c) => c !== name));
|
||||
return on;
|
||||
},
|
||||
contains: (name: string) => this.classes().includes(name),
|
||||
};
|
||||
|
||||
constructor(readonly tagName: string) {}
|
||||
|
||||
get ownerDocument(): typeof fakeDocument {
|
||||
return fakeDocument;
|
||||
}
|
||||
get parentElement(): FakeElement | null {
|
||||
return this.parentNode;
|
||||
}
|
||||
get children(): FakeElement[] {
|
||||
return this.childNodes.filter((child): child is FakeElement => typeof child !== "string");
|
||||
}
|
||||
get firstChild(): Child | null {
|
||||
return this.childNodes[0] ?? null;
|
||||
}
|
||||
get lastChild(): Child | null {
|
||||
return this.childNodes[this.childNodes.length - 1] ?? null;
|
||||
}
|
||||
get textContent(): string {
|
||||
return this.childNodes
|
||||
.map((child) => (typeof child === "string" ? child : child.textContent))
|
||||
.join("");
|
||||
}
|
||||
set textContent(value: string | null) {
|
||||
this.replaceChildren(...(value ? [String(value)] : []));
|
||||
}
|
||||
|
||||
private classes(): string[] {
|
||||
return (this.attributes.get("class") ?? "").split(/\s+/).filter(Boolean);
|
||||
}
|
||||
private setClasses(names: string[]): void {
|
||||
this.attributes.set("class", [...new Set(names)].join(" "));
|
||||
}
|
||||
private adopt(child: Child): Child {
|
||||
if (typeof child !== "string") {
|
||||
child.parentNode?.removeChild(child);
|
||||
child.parentNode = this;
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
setAttribute(name: string, value: unknown): void {
|
||||
this.attributes.set(name, String(value));
|
||||
}
|
||||
setAttributeNS(_ns: string | null, name: string, value: unknown): void {
|
||||
this.setAttribute(name, value);
|
||||
}
|
||||
getAttribute(name: string): string | null {
|
||||
return this.attributes.get(name) ?? null;
|
||||
}
|
||||
hasAttribute(name: string): boolean {
|
||||
return this.attributes.has(name);
|
||||
}
|
||||
removeAttribute(name: string): void {
|
||||
this.attributes.delete(name);
|
||||
}
|
||||
append(...nodes: Child[]): void {
|
||||
for (const node of nodes) this.childNodes.push(this.adopt(node));
|
||||
}
|
||||
appendChild<T extends Child>(node: T): T {
|
||||
this.append(node);
|
||||
return node;
|
||||
}
|
||||
prepend(...nodes: Child[]): void {
|
||||
this.childNodes.unshift(...nodes.map((node) => this.adopt(node)));
|
||||
}
|
||||
insertBefore<T extends Child>(node: T, reference: Child | null): T {
|
||||
const index = reference ? this.childNodes.indexOf(reference) : -1;
|
||||
if (index < 0) return this.appendChild(node);
|
||||
this.childNodes.splice(index, 0, this.adopt(node));
|
||||
return node;
|
||||
}
|
||||
removeChild<T extends Child>(node: T): T {
|
||||
const index = this.childNodes.indexOf(node);
|
||||
if (index >= 0) this.childNodes.splice(index, 1);
|
||||
if (typeof node !== "string") node.parentNode = null;
|
||||
return node;
|
||||
}
|
||||
replaceChildren(...nodes: Child[]): void {
|
||||
for (const child of [...this.childNodes]) this.removeChild(child);
|
||||
this.append(...nodes);
|
||||
}
|
||||
remove(): void {
|
||||
this.parentNode?.removeChild(this);
|
||||
}
|
||||
closest(selectors: string): FakeElement | null {
|
||||
for (let node: FakeElement | null = this; node; node = node.parentNode) {
|
||||
if (matches(node, selectors)) return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
querySelectorAll(selectors: string): FakeElement[] {
|
||||
const found: FakeElement[] = [];
|
||||
const walk = (node: FakeElement): void => {
|
||||
for (const child of node.children) {
|
||||
if (matches(child, selectors)) found.push(child);
|
||||
walk(child);
|
||||
}
|
||||
};
|
||||
walk(this);
|
||||
return found;
|
||||
}
|
||||
querySelector(selectors: string): FakeElement | null {
|
||||
return this.querySelectorAll(selectors)[0] ?? null;
|
||||
}
|
||||
addEventListener(): void {}
|
||||
removeEventListener(): void {}
|
||||
}
|
||||
|
||||
const fakeDocument = {
|
||||
createElementNS: (_ns: string, tag: string) => new FakeElement(tag),
|
||||
createElement: (tag: string) => new FakeElement(tag),
|
||||
createTextNode: (text: string) => text,
|
||||
// 오프스크린 SVG 는 문서에 안 붙어 있다 — 브라우저도 여기서 null 을 낸다(클립 해칭은 안 자름).
|
||||
getElementById: () => null,
|
||||
};
|
||||
|
||||
(globalThis as Record<string, unknown>).document = fakeDocument;
|
||||
|
||||
/* ── 범위 재기 ─────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
|
||||
@@ -552,3 +552,85 @@ export function overlayEntities(
|
||||
...harvest(pipeSvg, PIPE_STYLE, frame),
|
||||
];
|
||||
}
|
||||
|
||||
/** 서버가 도면에 실어 보내는 측점별 실좌표(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;
|
||||
|
||||
/**
|
||||
* 도면에 구조물 엔티티를 얹는다 (제자리 수정) — 화면(`_UI_Cad_Structures`)과 인쇄 Node(302-2)가 같이 씀.
|
||||
* 배치 메타가 없으면 아무것도 하지 않는다 — 구조물이 빠져도 도면 자체는 열려야 한다.
|
||||
*/
|
||||
export function placeStructureEntities(
|
||||
sections: CrossSection[],
|
||||
drawing: { entities: Record<string, unknown>[]; cross_placements?: CrossPlacement[] },
|
||||
): number {
|
||||
const placements = drawing.cross_placements ?? [];
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/* =============================================================================
|
||||
* B07_DesignDetail_Node_Dom.ts
|
||||
* 서버 Node 진입점이 쓰는 **최소 DOM** — 덧그림 그리기 코드가 오프스크린 SVG 에 쓰는 만큼만.
|
||||
* 불러오기만 하면 `globalThis.document` 가 선다(`_Cross_Extent_Node` · 인쇄 302-2 `B09_Draft_Print_Drawings_Node`).
|
||||
* ========================================================================== */
|
||||
|
||||
/* ── 최소 DOM: 그리기 코드가 쓰는 만큼만 ─────────────────────────────────── */
|
||||
|
||||
type Child = FakeElement | string;
|
||||
|
||||
/** 선택자 하나(`tag`, `tag[attr]`)가 이 요소에 맞나 — 그리기·수확이 쓰는 꼴만 안다. */
|
||||
function matches(element: FakeElement, selectors: string): boolean {
|
||||
return selectors.split(",").some((raw) => {
|
||||
const found = /^([\w-]*)(?:\[([\w-]+)\])?$/.exec(raw.trim());
|
||||
if (!found) return false;
|
||||
const [, tag, attribute] = found;
|
||||
if (tag && element.tagName !== tag) return false;
|
||||
return !attribute || element.attributes.has(attribute);
|
||||
});
|
||||
}
|
||||
|
||||
class FakeElement {
|
||||
readonly attributes = new Map<string, string>();
|
||||
readonly childNodes: Child[] = [];
|
||||
parentNode: FakeElement | null = null;
|
||||
readonly dataset: Record<string, string> = {};
|
||||
readonly style: Record<string, unknown> = { setProperty: () => undefined };
|
||||
readonly classList = {
|
||||
add: (...names: string[]) => this.setClasses([...this.classes(), ...names]),
|
||||
remove: (...names: string[]) =>
|
||||
this.setClasses(this.classes().filter((name) => !names.includes(name))),
|
||||
toggle: (name: string, force?: boolean) => {
|
||||
const on = force ?? !this.classes().includes(name);
|
||||
this.setClasses(on ? [...this.classes(), name] : this.classes().filter((c) => c !== name));
|
||||
return on;
|
||||
},
|
||||
contains: (name: string) => this.classes().includes(name),
|
||||
};
|
||||
|
||||
constructor(readonly tagName: string) {}
|
||||
|
||||
get ownerDocument(): typeof fakeDocument {
|
||||
return fakeDocument;
|
||||
}
|
||||
get parentElement(): FakeElement | null {
|
||||
return this.parentNode;
|
||||
}
|
||||
get children(): FakeElement[] {
|
||||
return this.childNodes.filter((child): child is FakeElement => typeof child !== "string");
|
||||
}
|
||||
get firstChild(): Child | null {
|
||||
return this.childNodes[0] ?? null;
|
||||
}
|
||||
get lastChild(): Child | null {
|
||||
return this.childNodes[this.childNodes.length - 1] ?? null;
|
||||
}
|
||||
get textContent(): string {
|
||||
return this.childNodes
|
||||
.map((child) => (typeof child === "string" ? child : child.textContent))
|
||||
.join("");
|
||||
}
|
||||
set textContent(value: string | null) {
|
||||
this.replaceChildren(...(value ? [String(value)] : []));
|
||||
}
|
||||
|
||||
private classes(): string[] {
|
||||
return (this.attributes.get("class") ?? "").split(/\s+/).filter(Boolean);
|
||||
}
|
||||
private setClasses(names: string[]): void {
|
||||
this.attributes.set("class", [...new Set(names)].join(" "));
|
||||
}
|
||||
private adopt(child: Child): Child {
|
||||
if (typeof child !== "string") {
|
||||
child.parentNode?.removeChild(child);
|
||||
child.parentNode = this;
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
setAttribute(name: string, value: unknown): void {
|
||||
this.attributes.set(name, String(value));
|
||||
}
|
||||
setAttributeNS(_ns: string | null, name: string, value: unknown): void {
|
||||
this.setAttribute(name, value);
|
||||
}
|
||||
getAttribute(name: string): string | null {
|
||||
return this.attributes.get(name) ?? null;
|
||||
}
|
||||
hasAttribute(name: string): boolean {
|
||||
return this.attributes.has(name);
|
||||
}
|
||||
removeAttribute(name: string): void {
|
||||
this.attributes.delete(name);
|
||||
}
|
||||
append(...nodes: Child[]): void {
|
||||
for (const node of nodes) this.childNodes.push(this.adopt(node));
|
||||
}
|
||||
appendChild<T extends Child>(node: T): T {
|
||||
this.append(node);
|
||||
return node;
|
||||
}
|
||||
prepend(...nodes: Child[]): void {
|
||||
this.childNodes.unshift(...nodes.map((node) => this.adopt(node)));
|
||||
}
|
||||
insertBefore<T extends Child>(node: T, reference: Child | null): T {
|
||||
const index = reference ? this.childNodes.indexOf(reference) : -1;
|
||||
if (index < 0) return this.appendChild(node);
|
||||
this.childNodes.splice(index, 0, this.adopt(node));
|
||||
return node;
|
||||
}
|
||||
removeChild<T extends Child>(node: T): T {
|
||||
const index = this.childNodes.indexOf(node);
|
||||
if (index >= 0) this.childNodes.splice(index, 1);
|
||||
if (typeof node !== "string") node.parentNode = null;
|
||||
return node;
|
||||
}
|
||||
replaceChildren(...nodes: Child[]): void {
|
||||
for (const child of [...this.childNodes]) this.removeChild(child);
|
||||
this.append(...nodes);
|
||||
}
|
||||
remove(): void {
|
||||
this.parentNode?.removeChild(this);
|
||||
}
|
||||
closest(selectors: string): FakeElement | null {
|
||||
for (let node: FakeElement | null = this; node; node = node.parentNode) {
|
||||
if (matches(node, selectors)) return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
querySelectorAll(selectors: string): FakeElement[] {
|
||||
const found: FakeElement[] = [];
|
||||
const walk = (node: FakeElement): void => {
|
||||
for (const child of node.children) {
|
||||
if (matches(child, selectors)) found.push(child);
|
||||
walk(child);
|
||||
}
|
||||
};
|
||||
walk(this);
|
||||
return found;
|
||||
}
|
||||
querySelector(selectors: string): FakeElement | null {
|
||||
return this.querySelectorAll(selectors)[0] ?? null;
|
||||
}
|
||||
addEventListener(): void {}
|
||||
removeEventListener(): void {}
|
||||
}
|
||||
|
||||
const fakeDocument = {
|
||||
createElementNS: (_ns: string, tag: string) => new FakeElement(tag),
|
||||
createElement: (tag: string) => new FakeElement(tag),
|
||||
createTextNode: (text: string) => text,
|
||||
// 오프스크린 SVG 는 문서에 안 붙어 있다 — 브라우저도 여기서 null 을 낸다(클립 해칭은 안 자름).
|
||||
getElementById: () => null,
|
||||
// 화면 모듈이 불러올 때 거는 문서 이벤트(누름 등) — 서버에는 사람이 없어 빈 함수
|
||||
addEventListener: () => undefined,
|
||||
removeEventListener: () => undefined,
|
||||
};
|
||||
|
||||
(globalThis as Record<string, unknown>).document = fakeDocument;
|
||||
|
||||
export {}; // 모듈로 둠 — 이름이 전역에 새지 않게
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -175,7 +175,8 @@ def test_옛_확정_장_원지반_잠금_풂_측점_층_안내(tmp_path: Path):
|
||||
|
||||
|
||||
def test_화면_덧그림_측점_층_design_ids():
|
||||
struct = (ROOT / "B07_DesignDetail/B07_DesignDetail_UI_Cad_Structures.ts").read_text(
|
||||
# 얹는 몸통 = Cross_Overlay.placeStructureEntities(302-2 · 화면 · 인쇄 Node 한 벌)
|
||||
struct = (ROOT / "B07_DesignDetail/B07_DesignDetail_Cross_Overlay.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert "layer_id?: string;" in struct and "design_ids?: string[];" in struct
|
||||
|
||||
Reference in New Issue
Block a user