feat(B07): DWG 변환기를 LibreDWG 로 교체·지원 버전 안내 추가
- ODA File Converter 제거 — 비회원 무료 사용이 비상업 용도로 제한되어 상용 서비스에 부적합 - LibreDWG dwg2dxf 를 별도 프로세스로 호출 (GPL 의무가 본 프로그램에 미치지 않는 방식), 경로는 .env LIBREDWG_DWG2DXF_PATH 또는 PATH 자동 탐색 - DWG 머리글로 형식 판정 — R2018 이후·미지원 형식은 변환 시도 없이 「2018 DWG 또는 DXF 로 저장」 안내 - 변환 실패·변환기 미설치도 같은 안내로 통일 - 형식 판정·안내 경로 테스트 추가 (tmp/tests, git 추적 제외) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,14 @@
|
|||||||
"""B07 외부 도각 파일 불러오기 — DXF(및 변환된 DWG)를 도각 JSON 으로 바꾼다.
|
"""B07 외부 도각 파일 불러오기 — DXF(및 변환된 DWG)를 도각 JSON 으로 바꾼다.
|
||||||
|
|
||||||
고객이 내는 도각은 DWG 일 확률이 높지만 DWG 는 비공개 형식이라 파이썬이 바로 못 읽는다.
|
고객이 내는 도각은 DWG 일 확률이 높지만 DWG 는 비공개 형식이라 파이썬이 바로 못 읽는다.
|
||||||
`.env` 의 `ODA_CONVERTER_PATH` 에 무료 변환기(ODA File Converter) 경로를 넣어 두면 DWG 를
|
**LibreDWG 의 `dwg2dxf`** 로 바꿔 읽는다 (2026-09-06 사용자 확정). ODA File Converter 는
|
||||||
DXF 로 바꿔 읽고, 없으면 「DXF 로 저장해 올려 달라」는 안내로 떨어진다 (2026-09-06 사용자 확정).
|
비회원 무료 사용이 **비상업 용도로 제한**돼 이 프로그램에는 쓰지 않는다.
|
||||||
|
|
||||||
|
LibreDWG 는 별도 실행 파일로만 부른다 — 라이브러리로 끌어안으면 GPL 이 이 프로그램까지
|
||||||
|
번진다. 별도 프로세스 호출은 그 의무가 생기지 않는다.
|
||||||
|
|
||||||
|
읽는 범위는 **R2018(AC1032) 까지**다. 그보다 새 형식이나 변환 실패는 「R2018 이하 또는
|
||||||
|
DXF 로 저장해 달라」는 안내로 떨어진다.
|
||||||
|
|
||||||
프로그램 값이 들어갈 자리는 여기서 알아맞히지 않는다 — 불러온 뒤 사용자가 자리표를
|
프로그램 값이 들어갈 자리는 여기서 알아맞히지 않는다 — 불러온 뒤 사용자가 자리표를
|
||||||
직접 놓는다. 좌표는 파일에 있는 그대로 쓴다(도각은 실치수 1:1 mm 로 그린다).
|
직접 놓는다. 좌표는 파일에 있는 그대로 쓴다(도각은 실치수 1:1 mm 로 그린다).
|
||||||
@@ -188,30 +194,58 @@ def dxf_to_entities(path: Path) -> list[dict[str, Any]]:
|
|||||||
return entities
|
return entities
|
||||||
|
|
||||||
|
|
||||||
|
# DWG 머리글의 형식 표시(앞 6바이트) — 읽을 수 있는 것과 사람이 읽을 이름.
|
||||||
|
_DWG_VERSIONS: dict[str, str] = {
|
||||||
|
"AC1014": "R14",
|
||||||
|
"AC1015": "2000",
|
||||||
|
"AC1018": "2004",
|
||||||
|
"AC1021": "2007",
|
||||||
|
"AC1024": "2010",
|
||||||
|
"AC1027": "2013",
|
||||||
|
"AC1032": "2018",
|
||||||
|
}
|
||||||
|
_SAVE_AS_GUIDE = (
|
||||||
|
"캐드에서 「다른 이름으로 저장」으로 AutoCAD 2018 DWG 또는 DXF 를 골라 저장한 뒤"
|
||||||
|
" 올려 주십시오."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def dwg_version(data: bytes) -> str | None:
|
||||||
|
"""DWG 머리글에서 형식 이름을 읽는다. 우리가 아는 형식이 아니면 None."""
|
||||||
|
return _DWG_VERSIONS.get(data[:6].decode("ascii", "ignore"))
|
||||||
|
|
||||||
|
|
||||||
|
def _dwg2dxf_path() -> str | None:
|
||||||
|
"""LibreDWG 변환기(dwg2dxf) 자리. `.env` 값이 먼저고, 없으면 PATH 에서 찾는다."""
|
||||||
|
configured = os.getenv("LIBREDWG_DWG2DXF_PATH", "").strip()
|
||||||
|
if configured:
|
||||||
|
return configured if Path(configured).is_file() else None
|
||||||
|
return shutil.which("dwg2dxf")
|
||||||
|
|
||||||
|
|
||||||
def _dwg_to_dxf(source: Path, work_dir: Path) -> Path:
|
def _dwg_to_dxf(source: Path, work_dir: Path) -> Path:
|
||||||
"""ODA File Converter 로 DWG 를 DXF 로 바꾼다. 변환기가 없으면 안내와 함께 실패."""
|
"""LibreDWG 로 DWG 를 DXF 로 바꾼다. 못 읽는 형식·변환기 없음은 안내와 함께 실패."""
|
||||||
converter = os.getenv("ODA_CONVERTER_PATH", "").strip()
|
with source.open("rb") as handle:
|
||||||
if not converter or not Path(converter).is_file():
|
version = dwg_version(handle.read(6))
|
||||||
raise ValueError(
|
if version is None:
|
||||||
"DWG 는 그대로 읽지 못합니다. 캐드에서 DXF 로 저장해 올려 주십시오."
|
raise ValueError(f"이 DWG 는 R2018 이후이거나 알 수 없는 형식입니다. {_SAVE_AS_GUIDE}")
|
||||||
" (서버에 ODA File Converter 를 두면 DWG 도 바로 읽습니다)"
|
converter = _dwg2dxf_path()
|
||||||
|
if not converter:
|
||||||
|
raise ValueError(f"이 서버는 아직 DWG 를 바로 읽지 못합니다. {_SAVE_AS_GUIDE}")
|
||||||
|
target = work_dir / "converted.dxf"
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
[converter, "-o", str(target), str(source)],
|
||||||
|
check=True,
|
||||||
|
timeout=180,
|
||||||
|
capture_output=True,
|
||||||
)
|
)
|
||||||
in_dir = work_dir / "in"
|
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc:
|
||||||
out_dir = work_dir / "out"
|
logger.info("B07 도각 DWG 변환 실패(%s): %s", version, exc)
|
||||||
in_dir.mkdir(parents=True, exist_ok=True)
|
raise ValueError(f"DWG({version}) 를 바꾸지 못했습니다. {_SAVE_AS_GUIDE}") from exc
|
||||||
out_dir.mkdir(parents=True, exist_ok=True)
|
if not target.is_file() or target.stat().st_size == 0:
|
||||||
shutil.copy(source, in_dir / source.name)
|
raise ValueError(f"DWG({version}) 를 바꾸지 못했습니다. {_SAVE_AS_GUIDE}")
|
||||||
# ODA 인자: 입력폴더 출력폴더 출력버전 출력형식 재귀 감사
|
return target
|
||||||
subprocess.run(
|
|
||||||
[converter, str(in_dir), str(out_dir), "ACAD2018", "DXF", "0", "1"],
|
|
||||||
check=True,
|
|
||||||
timeout=180,
|
|
||||||
capture_output=True,
|
|
||||||
)
|
|
||||||
converted = sorted(out_dir.glob("*.dxf"))
|
|
||||||
if not converted:
|
|
||||||
raise ValueError("DWG 를 DXF 로 바꾸지 못했습니다. 캐드에서 DXF 로 저장해 올려 주십시오.")
|
|
||||||
return converted[0]
|
|
||||||
|
|
||||||
|
|
||||||
def import_frame_file(filename: str, data: bytes) -> list[dict[str, Any]]:
|
def import_frame_file(filename: str, data: bytes) -> list[dict[str, Any]]:
|
||||||
|
|||||||
Reference in New Issue
Block a user