fix(parser): dynamically extract OLE2FRAME 4-corner coordinates from binary OLE stream header; remove hardcoded coordinates

This commit is contained in:
minsung
2026-07-30 16:10:43 +09:00
parent 963a90319e
commit 76f3e20582
12 changed files with 4865 additions and 17 deletions
+1
View File
@@ -0,0 +1 @@
function A(t){const e=x(t,s.__wbindgen_malloc),n=w,r=s.parse_dwg(e,n);if(r[2])throw h(r[1]);return h(r[0])}function W(){return{__proto__:null,"./acadrust_dwg_bg.js":{__proto__:null,__wbg_Error_92b29b0548f8b746:function(e,n){return Error(g(e,n))},__wbg_String_8564e559799eccda:function(e,n){const r=String(n),i=T(r,s.__wbindgen_malloc,s.__wbindgen_realloc),d=w;m().setInt32(e+4,d,!0),m().setInt32(e+0,i,!0)},__wbg___wbindgen_is_string_ea5e6cc2e4141dfe:function(e){return typeof e=="string"},__wbg___wbindgen_throw_344f42d3211c4765:function(e,n){throw new Error(g(e,n))},__wbg_new_32b398fb48b6d94a:function(){return new Array},__wbg_new_7796ffc7ed656783:function(){return new Map},__wbg_new_da52cf8fe3429cb2:function(){return new Object},__wbg_set_575dd786d51585f8:function(e,n,r){return e.set(n,r)},__wbg_set_6be42768c690e380:function(e,n,r){e[n]=r},__wbg_set_8a16b38e4805b298:function(e,n,r){e[n>>>0]=r},__wbindgen_cast_0000000000000001:function(e){return e},__wbindgen_cast_0000000000000002:function(e){return e},__wbindgen_cast_0000000000000003:function(e,n){return g(e,n)},__wbindgen_cast_0000000000000004:function(e){return BigInt.asUintN(64,e)},__wbindgen_init_externref_table:function(){const e=s.__wbindgen_externrefs,n=e.grow(4);e.set(0,void 0),e.set(n+0,void 0),e.set(n+1,null),e.set(n+2,!0),e.set(n+3,!1)}}}}let a=null;function m(){return(a===null||a.buffer.detached===!0||a.buffer.detached===void 0&&a.buffer!==s.memory.buffer)&&(a=new DataView(s.memory.buffer)),a}function g(t,e){return R(t>>>0,e)}let u=null;function _(){return(u===null||u.byteLength===0)&&(u=new Uint8Array(s.memory.buffer)),u}function x(t,e){const n=e(t.length*1,1)>>>0;return _().set(t,n/1),w=t.length,n}function T(t,e,n){if(n===void 0){const o=b.encode(t),f=e(o.length,1)>>>0;return _().subarray(f,f+o.length).set(o),w=o.length,f}let r=t.length,i=e(r,1)>>>0;const d=_();let c=0;for(;c<r;c++){const o=t.charCodeAt(c);if(o>127)break;d[i+c]=o}if(c!==r){c!==0&&(t=t.slice(c)),i=n(i,r,r=c+t.length*3,1)>>>0;const o=_().subarray(i+c,i+r),f=b.encodeInto(t,o);c+=f.written,i=n(i,r,c,1)>>>0}return w=c,i}function h(t){const e=s.__wbindgen_externrefs.get(t);return s.__externref_table_dealloc(t),e}let l=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0});l.decode();const E=2146435072;let y=0;function R(t,e){return y+=e,y>=E&&(l=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}),l.decode(),y=e),l.decode(_().subarray(t,t+e))}const b=new TextEncoder;"encodeInto"in b||(b.encodeInto=function(t,e){const n=b.encode(t);return e.set(n),{read:t.length,written:n.length}});let w=0,s;function M(t,e){return s=t.exports,a=null,u=null,s.__wbindgen_start(),s}async function O(t,e){if(typeof Response=="function"&&t instanceof Response){if(typeof WebAssembly.instantiateStreaming=="function")try{return await WebAssembly.instantiateStreaming(t,e)}catch(i){if(t.ok&&n(t.type)&&t.headers.get("Content-Type")!=="application/wasm")console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",i);else throw i}const r=await t.arrayBuffer();return await WebAssembly.instantiate(r,e)}else{const r=await WebAssembly.instantiate(t,e);return r instanceof WebAssembly.Instance?{instance:r,module:t}:r}function n(r){switch(r){case"basic":case"cors":case"default":return!0}return!1}}async function D(t){if(s!==void 0)return s;t!==void 0&&(Object.getPrototypeOf(t)===Object.prototype?{module_or_path:t}=t:console.warn("using deprecated parameters for the initialization function; pass a single object instead")),t===void 0&&(t=new URL("/assets/acadrust_dwg_bg-Bw7hOWaW.wasm",import.meta.url));const e=W();(typeof t=="string"||typeof Request=="function"&&t instanceof Request||typeof URL=="function"&&t instanceof URL)&&(t=fetch(t));const{instance:n,module:r}=await O(await t,e);return M(n)}const S="/assets/acadrust_dwg_bg-Bw7hOWaW.wasm";let p=null;function I(){return p||(p=D({module_or_path:S}).then(()=>{})),p}function B(t){return A(t)}export{I as initAcadrustParser,B as parseDwgAcadrust};
Binary file not shown.
File diff suppressed because one or more lines are too long
+361
View File
@@ -0,0 +1,361 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DWG/DXF · 모듈 조합 레시피 & 샘플</title>
<style>
:root {
--bg: #eef1f4; --panel: #fff; --ink: #1a2230; --ink-soft: #4a5568; --ink-faint: #8592a3;
--line: #d3dae2; --line-strong: #b7c1cd; --accent: #0e7c86; --accent-soft: #d3ecef;
--wasm: #b45309; --wasm-soft: #fbe8cf; --js: #0e7c86;
--grid: rgba(14,124,134,.06);
--shadow: 0 1px 2px rgba(26,34,48,.06), 0 8px 24px rgba(26,34,48,.06);
--mono: ui-monospace, "SF Mono", "Cascadia Code", Consolas, monospace;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Malgun Gothic", sans-serif;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0d1219; --panel: #141b25; --ink: #e6ecf3; --ink-soft: #a3b0c0; --ink-faint: #6b7a8d;
--line: #243040; --line-strong: #33445a; --accent: #3fd0dc; --accent-soft: #123038;
--wasm: #f0a03c; --wasm-soft: #33260f; --js: #3fd0dc; --grid: rgba(63,208,220,.05);
--shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 28px rgba(0,0,0,.35);
}
}
* { box-sizing: border-box; }
body {
margin: 0;
background:
linear-gradient(var(--grid) 1px, transparent 1px) 0 0 / 26px 26px,
linear-gradient(90deg, var(--grid) 1px, transparent 1px) 0 0 / 26px 26px,
var(--bg);
color: var(--ink); font-family: var(--sans); line-height: 1.6;
}
.wrap { max-width: 980px; margin: 0 auto; padding: 48px 24px 96px; }
header { border-bottom: 1px solid var(--line); padding-bottom: 28px; margin-bottom: 36px; }
.eyebrow {
font-family: var(--mono); font-size: 12px; letter-spacing: .14em; text-transform: uppercase;
color: var(--accent); display: flex; align-items: center; gap: 10px; margin-bottom: 14px;
}
.eyebrow::before { content: ""; width: 28px; height: 1px; background: var(--accent); }
h1 { font-size: clamp(26px, 4vw, 38px); line-height: 1.15; margin: 0 0 12px; letter-spacing: -.01em; }
h1 .dim { color: var(--ink-faint); font-weight: 400; }
.lede { font-size: 16px; color: var(--ink-soft); max-width: 64ch; margin: 0; }
.cta {
display: inline-flex; align-items: center; gap: 10px; margin-top: 20px;
font-family: var(--mono); font-size: 13px; font-weight: 600; text-decoration: none;
color: var(--accent); background: var(--panel); border: 1px solid var(--line);
border-left: 3px solid var(--accent); border-radius: 8px; padding: 10px 14px;
box-shadow: var(--shadow);
}
h2 {
font-size: 12px; font-family: var(--mono); letter-spacing: .12em; text-transform: uppercase;
color: var(--ink-faint); margin: 48px 0 16px; display: flex; align-items: center; gap: 12px;
}
h2 .num { color: var(--accent); }
h2::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.legend { display: flex; flex-wrap: wrap; gap: 16px; font-size: 13px; color: var(--ink-soft); margin: -4px 0 18px; }
.legend span { display: inline-flex; align-items: center; gap: 7px; }
.dot { width: 11px; height: 11px; border-radius: 3px; }
.dot.wasm { background: var(--wasm); }
.dot.js { background: var(--js); }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
.card {
background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
padding: 14px; box-shadow: var(--shadow); position: relative; overflow: hidden;
}
.card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--tag); }
.card.w { --tag: var(--wasm); }
.card.j { --tag: var(--js); }
.card .k {
font-family: var(--mono); font-size: 13px; font-weight: 600; margin-bottom: 6px;
display: flex; justify-content: space-between; gap: 8px; align-items: baseline;
}
.badge {
font-family: var(--mono); font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase;
padding: 2px 6px; border-radius: 4px; font-weight: 600;
}
.badge.w { background: var(--wasm-soft); color: var(--wasm); }
.badge.j { background: var(--accent-soft); color: var(--js); }
.card .d { font-size: 13px; color: var(--ink-soft); line-height: 1.45; }
.card .path { font-family: var(--mono); font-size: 11px; color: var(--ink-faint); margin-top: 8px; word-break: break-all; }
.flow {
background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
padding: 22px 16px; box-shadow: var(--shadow); overflow-x: auto;
}
.pipe { display: flex; align-items: stretch; min-width: 720px; }
.node {
flex: 1; background: var(--bg); border: 1px solid var(--line-strong); border-radius: 9px;
padding: 12px; display: flex; flex-direction: column; gap: 4px; min-width: 0;
}
.node.hl { border-color: var(--accent); background: var(--accent-soft); }
.node.w { border-color: var(--wasm); }
.node .tier { font-family: var(--mono); font-size: 10px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint); }
.node.hl .tier { color: var(--accent); }
.node.w .tier { color: var(--wasm); }
.node .name { font-family: var(--mono); font-size: 12.5px; font-weight: 600; word-break: break-all; }
.node .sub { font-size: 12px; color: var(--ink-soft); }
.arrow { flex: 0 0 32px; display: flex; align-items: center; justify-content: center; color: var(--accent); font-size: 18px; }
.flow-note { font-size: 12.5px; color: var(--ink-faint); margin-top: 14px; font-family: var(--mono); }
.flow-note b { color: var(--accent); }
pre {
background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 16px 18px;
overflow-x: auto; font-family: var(--mono); font-size: 12.5px; color: var(--ink-soft); line-height: 1.55;
box-shadow: var(--shadow); margin: 0;
}
pre .c { color: var(--accent); }
pre .w { color: var(--wasm); }
pre .cm { color: var(--ink-faint); }
.steps { display: grid; gap: 12px; }
.step {
background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
padding: 16px 18px; box-shadow: var(--shadow); display: grid; grid-template-columns: 36px 1fr; gap: 12px;
}
.step .n {
font-family: var(--mono); font-weight: 700; font-size: 18px; color: var(--accent);
width: 36px; height: 36px; border-radius: 8px; background: var(--accent-soft);
display: flex; align-items: center; justify-content: center;
}
.step h3 { margin: 0 0 4px; font-size: 15px; }
.step p { margin: 0; font-size: 13.5px; color: var(--ink-soft); }
.step code { font-family: var(--mono); font-size: .9em; background: var(--bg); padding: 1px 5px; border-radius: 4px; border: 1px solid var(--line); }
code { font-family: var(--mono); font-size: .9em; background: var(--bg); padding: 1px 5px; border-radius: 4px; border: 1px solid var(--line); }
footer {
margin-top: 56px; padding-top: 18px; border-top: 1px solid var(--line);
font-family: var(--mono); font-size: 12px; color: var(--ink-faint);
display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px;
}
a { color: var(--accent); }
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="eyebrow">compose · dont invent</div>
<h1>DWG · DXF 모듈 조합 <span class="dim">레시피</span></h1>
<p class="lede">
전체 제품이 아니라, 지도에 있는 <b>재사용 모듈만</b> 골라 조립하면 2D CAD 뷰어가 됩니다.
아래 카드 → 파이프라인 → 샘플 코드 순으로 옮기면 다른 프로젝트에서도 동일하게 동작합니다.
</p>
<a class="cta" href="/">
▶ 실행 샘플 · 이 프로젝트 루트 (npm run dev)
</a>
<p style="margin:10px 0 0; font-family:var(--mono); font-size:12px; color:var(--ink-faint)">
cd dwg-dxf-viewer-sample && npm i && npm run dev → http://localhost:5173
</p>
</header>
<!-- 01 modules -->
<h2><span class="num">01</span> 조합에 쓰는 모듈 (지도 카드)</h2>
<div class="legend">
<span><i class="dot wasm"></i> WASM 파서</span>
<span><i class="dot js"></i> JS 모듈 · 렌더러</span>
</div>
<div class="grid">
<div class="card w">
<div class="k">acadrust-dwg <span class="badge w">wasm</span></div>
<div class="d">DWG 바이너리 → JSON parseResult. 외부 네트워크 없음.</div>
<div class="path">src/viewer2d/acadrust-dwg/*.wasm (이 프로젝트)</div>
</div>
<div class="card j">
<div class="k">dwgParser / acadrustParser <span class="badge j">wrap</span></div>
<div class="d">WASM 1회 init + <code>parseDwgBuffer(bytes)</code>.</div>
<div class="path">dwgParser.ts · acadrustParser.ts</div>
</div>
<div class="card j">
<div class="k">dxf-parser <span class="badge j">npm</span></div>
<div class="d">DXF 텍스트 파싱 (MIT). HATCH 기본 미지원.</div>
<div class="path">npm: dxf-parser</div>
</div>
<div class="card j">
<div class="k">dxfHatchHandler <span class="badge j">patch</span></div>
<div class="d"><code>registerEntityHandler</code>로 HATCH 보강.</div>
<div class="path">dxfHatchHandler.js</div>
</div>
<div class="card j">
<div class="k">dxfAdapter <span class="badge j">adapt</span></div>
<div class="d">dxf-parser 출력 → Viewer2D 공통 스키마.</div>
<div class="path">dxfAdapter.js</div>
</div>
<div class="card j">
<div class="k">Viewer2D <span class="badge j">render</span></div>
<div class="d">Orthographic three 씬. LINE/ARC/HATCH/TEXT/INSERT…</div>
<div class="path">Viewer2D.js</div>
</div>
<div class="card j">
<div class="k">three <span class="badge j">peer</span></div>
<div class="d">WebGLRenderer + OrbitControls (pan/zoom).</div>
<div class="path">npm: three ≥0.160</div>
</div>
<div class="card j">
<div class="k">slugText + font <span class="badge j">text</span></div>
<div class="d">opentype.js → GPU 텍스트. TTF를 호스트가 서빙.</div>
<div class="path">slugText.ts · /fonts/*.ttf</div>
</div>
<div class="card j">
<div class="k">ext2d <span class="badge j">util</span></div>
<div class="d"><code>isDwg</code> / <code>isDxf</code> / <code>is2D</code> — 의존 zero.</div>
<div class="path">ext2d.ts</div>
</div>
</div>
<!-- 02 pipeline -->
<h2><span class="num">02</span> 조립 파이프라인</h2>
<div class="flow">
<div class="pipe">
<div class="node">
<div class="tier">Host</div>
<div class="name">File · URL · Drop</div>
<div class="sub">.dwg / .dxf</div>
</div>
<div class="arrow"></div>
<div class="node hl">
<div class="tier">ext2d</div>
<div class="name">isDwg / isDxf</div>
<div class="sub">분기</div>
</div>
<div class="arrow"></div>
<div class="node w">
<div class="tier">Parser A or B</div>
<div class="name">acadrust · dxf-parser</div>
<div class="sub">(+ hatch · adapter)</div>
</div>
<div class="arrow"></div>
<div class="node hl">
<div class="tier">Contract</div>
<div class="name">CadParseResult</div>
<div class="sub">공통 JSON</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tier">Viewer2D</div>
<div class="name">.load(result)</div>
<div class="sub">three 씬</div>
</div>
</div>
<div class="flow-note">
<b>핵심 계약</b>: 파서는 파일을 안 열고, 렌더러는 바이트를 안 연다.
중간은 항상 <code>CadParseResult</code> 하나.
</div>
</div>
<!-- 03 recipe steps -->
<h2><span class="num">03</span> 다른 프로젝트에 옮기는 순서</h2>
<div class="steps">
<div class="step">
<div class="n">1</div>
<div>
<h3>모듈 소스 복사</h3>
<p>
이 샘플의 <code>src/viewer2d/</code> 전체를 복사.
npm: <code>three</code>, <code>dxf-parser</code>, <code>opentype.js</code>.
(원본 소스는 형제 폴더 <code>hmwebviewer/src/viewer2d</code>.)
</p>
</div>
</div>
<div class="step">
<div class="n">2</div>
<div>
<h3>정적 자산</h3>
<p>
<code>public/fonts/NanumGothic-Regular.ttf</code> (한글 TEXT).
WASM은 소스가 <code>?url</code>로 import — Vite면 <code>assetsInclude: ['**/*.wasm']</code>.
</p>
</div>
</div>
<div class="step">
<div class="n">3</div>
<div>
<h3>아래 스니펫처럼 조합</h3>
<p>
래퍼 클래스 없이 모듈을 직접 import 해도 됩니다.
실행 가능한 전체 예: 이 프로젝트 <code>src/main.ts</code>.
</p>
</div>
</div>
<div class="step">
<div class="n">4</div>
<div>
<h3>호스트 UI만 붙이기</h3>
<p>
드롭존 · 툴바 · 레이어 패널은 모듈이 아님.
샘플의 ⑤ 영역만 교체하면 디자인 시스템에 맞게 붙일 수 있음.
</p>
</div>
</div>
</div>
<!-- 04 code -->
<h2><span class="num">04</span> 최소 조합 코드 (샘플과 동일 패턴)</h2>
<pre><span class="cm">// ① 모듈 지도 카드 → import</span>
<span class="c">import</span> { Viewer2D } <span class="c">from</span> './viewer2d/Viewer2D.js';
<span class="c">import</span> { parseDwgBuffer } <span class="c">from</span> './viewer2d/dwgParser'; <span class="cm">// acadrust-dwg</span>
<span class="c">import</span> { parseDxfBuffer } <span class="c">from</span> './viewer2d/parseDxf'; <span class="cm">// dxf-parser+hatch+adapter</span>
<span class="c">import</span> { isDwg, isDxf } <span class="c">from</span> './viewer2d/ext2d';
<span class="c">import</span> { setDefaultFontUrl } <span class="c">from</span> './viewer2d/slugText';
setDefaultFontUrl('/fonts/NanumGothic-Regular.ttf');
<span class="cm">// ② 렌더러</span>
<span class="c">const</span> viewer = <span class="c">new</span> Viewer2D(document.getElementById('app')!);
<span class="cm">// ③ 파서 조합</span>
<span class="c">async function</span> parseCad(buf: ArrayBuffer, name: string) {
<span class="c">if</span> (isDxf(name)) <span class="c">return</span> parseDxfBuffer(buf); <span class="cm">// dxf-parser 경로</span>
<span class="c">if</span> (isDwg(name)) <span class="c">return</span> parseDwgBuffer(<span class="c">new</span> Uint8Array(buf)); <span class="cm">// WASM 경로</span>
<span class="c">throw new</span> Error('need .dwg or .dxf name');
}
<span class="cm">// ④ 계약: parseResult → load</span>
<span class="c">async function</span> open(file: File) {
<span class="c">const</span> result = <span class="c">await</span> parseCad(<span class="c">await</span> file.arrayBuffer(), file.name);
viewer.load(result);
viewer.resize();
viewer.fit();
}</pre>
<h2><span class="num">05</span> DWG 경로 vs DXF 경로</h2>
<div class="grid">
<div class="card w">
<div class="k">.dwg 경로 <span class="badge w">wasm</span></div>
<div class="d">
<code>parseDwgBuffer</code><br>
→ acadrustParser.init<br>
<span style="color:var(--wasm)">parse_dwg(bytes)</span><br>
→ CadParseResult
</div>
<div class="path">Sample DWG 버튼으로 검증</div>
</div>
<div class="card j">
<div class="k">.dxf 경로 <span class="badge j">js</span></div>
<div class="d">
<code>parseDxfBuffer</code><br>
→ decodeDxf (UTF-8 / CP949…)<br>
→ DxfParser + HatchHandler<br>
→ dxfToParseResult
</div>
<div class="path">Sample DXF 버튼으로 검증</div>
</div>
</div>
<h2><span class="num">06</span> 샘플 위치</h2>
<pre style="white-space:pre-wrap"><span class="c">dwg-dxf-viewer-sample/</span> ← hmwebviewer 형제 폴더
src/main.ts ← 모듈을 직접 import 해서 조립 (핵심)
src/viewer2d/ ← acadrust-dwg · Viewer2D · dxf-*
index.html ← 툴바 + Module recipe 패널
public/fonts/ ← NanumGothic (slugText)
public/samples/ ← BasicSample.dwg · simple.dxf
docs/modules-dwg-dxf.html
<span class="c">실행</span>
cd ../dwg-dxf-viewer-sample # (hmwebviewer 기준 형제)
npm install
npm run dev → http://localhost:5173</pre>
<footer>
<span>DWG/DXF only · sibling of hmwebviewer</span>
<span>no 3D / SHP / R2</span>
</footer>
</div>
</body>
</html>
Binary file not shown.
+143
View File
@@ -0,0 +1,143 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DWG/DXF · 모듈 조합 샘플</title>
<style>
:root {
--bg: #0d1117; --panel: #161b22; --ink: #e6edf3; --muted: #8b949e;
--accent: #3fb950; --wasm: #f0a03c; --line: #30363d; --danger: #f85149;
--mono: ui-monospace, "Cascadia Code", Consolas, monospace;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Malgun Gothic", sans-serif;
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink); font-family: var(--sans); }
#app { position: fixed; inset: 0; }
#toolbar {
position: fixed; top: 12px; left: 12px; right: 12px; z-index: 10;
display: flex; flex-wrap: wrap; gap: 8px; align-items: center; pointer-events: none;
}
#toolbar > * { pointer-events: auto; }
button, label.btn {
background: var(--panel); color: var(--ink); border: 1px solid var(--line);
border-radius: 8px; padding: 8px 12px; font: 13px var(--sans); cursor: pointer;
}
button:hover, label.btn:hover { border-color: var(--accent); }
button.primary { background: #238636; border-color: #2ea043; }
button.wasm { border-color: var(--wasm); color: var(--wasm); }
label.btn input { display: none; }
#status {
margin-left: auto; font: 12px var(--mono); color: var(--muted);
background: rgba(22,27,34,.85); border: 1px solid var(--line);
border-radius: 8px; padding: 8px 12px; max-width: min(48vw, 480px);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#status.err { color: var(--danger); }
#recipe {
position: fixed; bottom: 14px; left: 14px; z-index: 10; max-width: 340px;
background: rgba(22,27,34,.94); border: 1px solid var(--line); border-radius: 10px;
padding: 12px 14px; font-size: 12px; line-height: 1.45; color: var(--muted);
box-shadow: 0 8px 24px rgba(0,0,0,.35);
}
#recipe h3 {
margin: 0 0 8px; font: 600 11px var(--mono); letter-spacing: .1em;
text-transform: uppercase; color: var(--accent);
}
#recipe ol { margin: 0; padding-left: 18px; }
#recipe li { margin-bottom: 4px; }
#recipe code { font-family: var(--mono); font-size: 11px; color: var(--ink); }
#recipe .mod-w { color: var(--wasm); }
#recipe .mod-j { color: var(--accent); }
#recipe a { color: var(--accent); font-family: var(--mono); font-size: 11px; }
#layers {
position: fixed; top: 56px; right: 12px; width: 200px; max-height: 50vh;
overflow: auto; z-index: 10; background: rgba(22,27,34,.95);
border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px;
font-size: 12px; display: none;
}
#layers h3 { margin: 0 0 8px; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
#layers label { display: flex; gap: 8px; align-items: center; margin: 4px 0; cursor: pointer; }
#layers .swatch { width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; }
#spaces {
display: none; align-items: center; gap: 0;
background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
overflow: hidden;
}
#spaces button {
border: none; border-radius: 0; border-right: 1px solid var(--line);
background: transparent; padding: 8px 12px; margin: 0;
}
#spaces button:last-child { border-right: none; }
#spaces button.active {
background: #238636; border-color: #2ea043; color: #fff;
}
#spaces button:hover:not(.active) { border-color: transparent; color: var(--accent); }
/* CAD 좌표 — 항상 우측 하단 (툴바 status 와 별개) */
#coords {
position: fixed;
right: 12px;
bottom: 12px;
left: auto;
top: auto;
z-index: 20;
font: 13px/1.35 var(--mono);
color: var(--ink);
background: rgba(13,17,23,.95);
border: 1px solid var(--line);
border-radius: 8px;
padding: 10px 14px;
min-width: 240px;
text-align: left;
pointer-events: none;
letter-spacing: 0.02em;
box-shadow: 0 8px 28px rgba(0,0,0,.45);
user-select: none;
}
#coords .lbl { color: var(--accent); margin-right: 6px; font-weight: 600; }
#coords .sep { color: var(--line); margin: 0 12px; }
</style>
<script type="module" crossorigin src="/assets/index-B3deAy7F.js"></script>
</head>
<body>
<div id="app"></div>
<div id="toolbar">
<label class="btn">
Open file
<input id="file" type="file" accept=".dwg,.dxf" />
</label>
<button id="sample" class="primary wasm" type="button" title="acadrust-dwg WASM">Sample DWG</button>
<button id="sampleDxf" class="primary" type="button" title="dxf-parser">Sample DXF</button>
<div id="spaces" title="Model Space / Paper Space (Layout)"></div>
<button id="fit" type="button">Fit (F)</button>
<button id="theme" type="button">Theme</button>
<button id="layersBtn" type="button">Layers</button>
<div id="status">ready</div>
</div>
<div id="coords" title="마우스 위치 CAD 좌표 (Model=UCS, Layout=Paper 도면단위)">
<span class="lbl">X</span><span id="coordX"></span>
<span class="sep">|</span>
<span class="lbl">Y</span><span id="coordY"></span>
</div>
<div id="layers"><h3>Layers</h3><div id="layerList"></div></div>
<aside id="recipe">
<h3>Module recipe</h3>
<ol>
<li><code class="mod-j">ext2d</code> — .dwg / .dxf 분기</li>
<li><code class="mod-w">acadrust-dwg</code> 또는 <code class="mod-j">dxf-parser</code></li>
<li><code class="mod-j">dxfAdapter</code> — DXF → 공통 스키마</li>
<li><code class="mod-j">Viewer2D</code> + <code class="mod-j">three</code> 렌더</li>
<li><code class="mod-j">slugText</code> + TTF 폰트</li>
</ol>
<p style="margin:10px 0 0">
코드: <code>src/main.ts</code> ·
지도: <a href="/docs/modules-dwg-dxf.html" target="_blank">docs/modules-dwg-dxf.html</a>
</p>
</aside>
</body>
</html>
Binary file not shown.
Binary file not shown.
+146
View File
@@ -0,0 +1,146 @@
0
SECTION
2
HEADER
9
$ACADVER
1
AC1015
9
$INSUNITS
70
4
0
ENDSEC
0
SECTION
2
TABLES
0
TABLE
2
LAYER
0
LAYER
2
0
70
0
62
7
0
LAYER
2
OUTLINE
70
0
62
1
0
ENDTAB
0
ENDSEC
0
SECTION
2
BLOCKS
0
ENDSEC
0
SECTION
2
ENTITIES
0
LINE
8
OUTLINE
10
0.0
20
0.0
30
0.0
11
100.0
21
0.0
31
0.0
0
LINE
8
OUTLINE
10
100.0
20
0.0
30
0.0
11
100.0
21
60.0
31
0.0
0
LINE
8
OUTLINE
10
100.0
20
60.0
30
0.0
11
0.0
21
60.0
31
0.0
0
LINE
8
OUTLINE
10
0.0
20
60.0
30
0.0
11
0.0
21
0.0
31
0.0
0
CIRCLE
8
0
10
50.0
20
30.0
30
0.0
40
15.0
0
TEXT
8
0
10
10.0
20
70.0
30
0.0
40
5.0
1
Hello DWG/DXF package
0
ENDSEC
0
EOF