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:
@@ -62,10 +62,12 @@ def _entity_id(index: int) -> str:
|
||||
|
||||
|
||||
def _color(entity: Any) -> str:
|
||||
"""DXF 색 번호를 화면 색으로. 도면층 색(BYLAYER)이면 기본색을 쓴다."""
|
||||
"""DXF 색 번호를 화면 색으로. BYLAYER 면 레이어 색(488 · B07 붙이기 · M02 불러오기 같이) · BYBLOCK 은 기본색."""
|
||||
try:
|
||||
aci = int(entity.dxf.color)
|
||||
if aci in (0, 256): # BYBLOCK · BYLAYER
|
||||
if aci == 256: # BYLAYER
|
||||
return _layer_color(entity.doc, entity.dxf.layer) or _DEFAULT_COLOR
|
||||
if aci == 0: # BYBLOCK
|
||||
return _DEFAULT_COLOR
|
||||
red, green, blue = ezdxf_colors.aci2rgb(aci)
|
||||
return f"#{red:02x}{green:02x}{blue:02x}"
|
||||
|
||||
@@ -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'] },
|
||||
],
|
||||
|
||||
@@ -7,6 +7,7 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import catalog from "../../../B07_DesignDetail/openwebcad/src/commands/command-catalog.json";
|
||||
import { getAllCommands } from "../../../B07_DesignDetail/openwebcad/src/commands/registry";
|
||||
import { RIBBON_TABS } from "../../../B07_DesignDetail/openwebcad/src/ribbon/ribbon.config";
|
||||
|
||||
describe("command-catalog.json", () => {
|
||||
it("registry 와 같은 명령 · 한글 이름 · 기본 별칭 · 차례", () => {
|
||||
@@ -37,4 +38,10 @@ describe("command-catalog.json", () => {
|
||||
expect(words.filter((w) => /도면층|선가중치/.test(w))).toEqual([]);
|
||||
expect(catalog.find((c) => c.id === "LAYER")?.label).toContain("레이어");
|
||||
});
|
||||
|
||||
it("488 — 삽입 탭에 [DWG 불러오기](IMPORT)", () => {
|
||||
const insert = RIBBON_TABS.find((tab) => tab.id === "insert");
|
||||
expect(insert?.panels.flatMap((panel) => panel.big ?? [])).toContain("IMPORT");
|
||||
expect(catalog.find((c) => c.id === "IMPORT")?.label).toBe("DWG 불러오기");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,14 @@ from pathlib import Path
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Frame_Import import dxf_text_to_entities
|
||||
import io
|
||||
|
||||
import ezdxf
|
||||
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Frame_Import import (
|
||||
dxf_text_to_entities,
|
||||
import_frame_file,
|
||||
)
|
||||
from B07_DesignDetail.B07_DesignDetail_Router_Frame import router
|
||||
|
||||
SAMPLE = Path("resources/tester/fixtures/b07_webclip_sample.dxf").read_text(encoding="utf-8")
|
||||
@@ -30,6 +37,23 @@ def test_engine_keeps_layer_names_and_shapes():
|
||||
assert line["shapeData"]["endPoint"] == {"x": 100.0, "y": 0.0} # 원좌표 그대로
|
||||
|
||||
|
||||
def test_bylayer_color_is_layer_color():
|
||||
"""488 — BYLAYER 도형 = 레이어 색(B07 붙이기 · M02 불러오기 같은 길) · 제 색은 그대로."""
|
||||
document = ezdxf.new()
|
||||
document.layers.add("빨강", color=1)
|
||||
space = document.modelspace()
|
||||
space.add_line((0, 0), (10, 0), dxfattribs={"layer": "빨강"}) # 색 = BYLAYER(256)
|
||||
space.add_line((0, 5), (10, 5), dxfattribs={"layer": "빨강", "color": 3})
|
||||
buffer = io.StringIO()
|
||||
document.write(buffer)
|
||||
text = buffer.getvalue()
|
||||
for entities in (
|
||||
dxf_text_to_entities(text)[0],
|
||||
import_frame_file("a.dxf", text.encode("utf-8")),
|
||||
):
|
||||
assert [entity["lineColor"] for entity in entities] == ["#ff0000", "#00ff00"]
|
||||
|
||||
|
||||
def test_route_returns_entities():
|
||||
response = client.post("/api/projects/dxf-clip", json={"text": SAMPLE})
|
||||
assert response.status_code == 200
|
||||
|
||||
Reference in New Issue
Block a user