feat(488-3 DWG 불러오기): 삽입 탭 [DWG 불러오기](IMPORT) — 끌어다 놓기와 같은 서버 변환 · 원좌표 · 골라진 채 · 화면 맞춤 · 되돌리기 한 번 · 파일 입력 문서에 숨겨 붙임 · ByLayer 도형 = 레이어 색(B07 붙이기 · M02 불러오기 같은 _color) · 시험 catalog · test_b07_dxf_clip 한 건씩

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 22:17:13 +09:00
co-authored by Claude Opus 5.5
parent 99df7c2512
commit 6695dfaee9
8 changed files with 84 additions and 7 deletions
@@ -136,6 +136,7 @@
{"id": "IMAGEATTACH", "label": "이미지 부착", "aliases": ["IAT"]},
{"id": "BLOCK", "label": "블록 작성", "aliases": ["B"]},
{"id": "INSERT", "label": "블록 삽입", "aliases": ["I"]},
{"id": "IMPORT", "label": "DWG 불러오기", "aliases": []},
{"id": "SELECT", "label": "선택", "aliases": []},
{"id": "ZOOM", "label": "범위 줌", "aliases": ["Z"]},
{"id": "ZOOMIN", "label": "확대", "aliases": []},
@@ -4,6 +4,7 @@ import { saveBlockFromEntities } from '../blocks/block-library';
import { setBlockLibraryVisible } from '../components/ui-state';
import { importImageFromFile } from '../helpers/import-export-handlers/import-image-from-file';
import { pickFile } from '../helpers/pick-file';
import { importDrawingFile } from '../integration/dxf-clip-paste';
import { getActiveToolActor, getSelectedEntities } from '../state';
import { Tool } from '../tools';
import { imageImportToolStateMachine } from '../tools/image-import-tool';
@@ -58,4 +59,14 @@ export const INSERT_COMMANDS: CadCommand[] = [
return '블록 라이브러리';
},
},
{
id: 'IMPORT',
label: 'DWG 불러오기',
glyph: '📂',
hint: 'DWG · DXF 파일을 지금 도면에 원좌표 그대로 더한다(골라진 채 · 되돌리기 한 번에 빠짐)',
run: () => {
pickFile('.dwg,.dxf', importDrawingFile);
return 'DWG · DXF 파일 선택';
},
},
];
@@ -1,11 +1,20 @@
/** 파일 선택창을 띄우고 고른 파일 하나를 돌려준다. 취소하면 아무 일도 없다. */
/**
* 파일 선택창을 띄우고 고른 파일 하나를 돌려준다. 취소하면 아무 일도 없다.
* 입력은 고르는 동안 문서에 숨겨 붙임(`.cad-pick-file` · 자동 시험 업로드가 닿게) · 고르거나 취소하면 뗌.
*/
export function pickFile(accept: string, onPicked: (file: File) => void): void {
const input = document.createElement('input');
input.type = 'file';
input.accept = accept;
input.hidden = true;
input.className = 'cad-pick-file';
document.querySelectorAll('.cad-pick-file').forEach((old) => old.remove());
input.addEventListener('change', () => {
const file = input.files?.[0];
input.remove();
if (file) onPicked(file);
});
input.addEventListener('cancel', () => input.remove());
document.body.append(input);
input.click();
}
@@ -1,8 +1,8 @@
/**
* 오토캐드 도형 붙이기 — 두 길 모두 서버가 DXF 를 도형으로 바꿔 현재 도면에 원좌표 그대로 붙이고
* 고른 상태로 둔다(되돌리기 한 번에 빠짐).
* - 끌어다 놓기: 오토캐드 WBLOCK(DWG) · DXFOUT(DXF) 로 저장한 파일, 또는 도면 파일 통째.
* 끄는 동안 `body.cad-file-dragging` 으로 흐린 테두리.
* - 끌어다 놓기 · [삽입 → DWG 불러오기](IMPORT · 488): 오토캐드 WBLOCK(DWG) · DXFOUT(DXF) 로 저장한 파일,
* 또는 도면 파일 통째 · 붙인 범위로 화면 맞춤. 끄는 동안 `body.cad-file-dragging` 으로 흐린 테두리.
* - Ctrl+V: 시스템 클립보드 글이 DXF 면 같은 길 · 아니면 앱 안 붙여넣기(PASTECLIP).
* 키 처리(`input-controller.ts`)는 Ctrl+V 의 기본 동작을 막지 않아 브라우저가 `paste` 를 쏜다 —
* `paste` 이벤트의 글은 권한 없이 읽힘(iframe 안에서도).
@@ -10,10 +10,14 @@
import { toast } from 'react-toastify';
import { runCommandInput } from '../commands/run-command';
import { hasClipboardContent } from '../helpers/cad-clipboard';
import { fitView } from '../drawControllers/fit-view';
import { getEntitiesAndLayersFromJsonObject } from '../helpers/import-export-handlers/import-entities-from-json';
import { changeView } from '../inputController/zoom-prompt';
import {
addEntities,
getLayers,
getScreenCanvasDrawController,
getSelectedEntities,
isDrawingReadOnly,
setLayers,
setSelectedEntityIds,
@@ -73,6 +77,24 @@ function refuseReadOnly(): boolean {
return true;
}
/** 붙인 것(골라진 채)이 화면에 다 보이게 — ZOOM 이전(P)으로 돌아감 */
function fitToSelection(count: number): number {
const picked = getSelectedEntities();
const controller = getScreenCanvasDrawController();
if (picked.length)
changeView(() => {
const { zoom, offset } = fitView(picked, controller.getCanvasSize());
controller.setScreenScale(zoom);
controller.setScreenOffset(offset);
});
return count;
}
/** DWG · DXF 파일 → 지금 도면에 더함(원좌표 · 골라진 채 · 화면 맞춤 · 되돌리기 한 번) */
export function importDrawingFile(file: File): void {
if (!refuseReadOnly()) reportPaste(pasteDrawingFile(file).then(fitToSelection));
}
export function registerDxfClipPaste(): void {
document.addEventListener('paste', (event: ClipboardEvent) => {
if ((event.target as HTMLElement | null)?.closest?.('input, textarea, select')) return;
@@ -101,6 +123,6 @@ export function registerDxfClipPaste(): void {
event.preventDefault();
setDragging(false);
const file = event.dataTransfer?.files[0];
if (file && !refuseReadOnly()) reportPaste(pasteDrawingFile(file));
if (file) importDrawingFile(file);
});
}
@@ -144,6 +144,7 @@ export const RIBBON_TABS: RibbonTab[] = [
id: 'insert',
label: '삽입',
panels: [
{ label: '가져오기', big: ['IMPORT'] },
{ label: '블록', big: ['INSERT'], commands: ['BLOCK'] },
{ label: '참조', commands: ['IMAGEATTACH'] },
],