파일을 "/" 로 업로드

This commit is contained in:
hylee4 committed 2026-10-29 09:08:50 +09:00
commit 3340359817
5 files changed
+3303

No files matched your search

+3034
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
!function(e,a){"object"==typeof exports&&"object"==typeof module?module.exports=a():"function"==typeof define&&define.amd?define([],a):"object"==typeof exports?exports.DxfParser=a():e.DxfParser=a()}("undefined"!=typeof self?self:this,(function(){return(()=>{var e={43:function(e,a,t){var r,n;!function(s,o){"use strict";void 0===(n="function"==typeof(r=function(){var e=function(){},a="undefined",t=typeof window!==a&&typeof window.navigator!==a&&/Trident\/|MSIE /.test(window.navigator.userAgent),r=["trace","debug","info","warn","error"];function n(e,a){var t=e[a];if("function"==typeof t.bind)return t.bind(e);try{return Function.prototype.bind.call(t,e)}catch(a){return function(){return Function.prototype.apply.apply(t,[e,arguments])}}}function s(){console.log&&(console.log.apply?console.log.apply(console,arguments):Function.prototype.apply.apply(console.log,[console,arguments])),console.trace&&console.trace()}function o(r){return"debug"===r&&(r="log"),typeof console!==a&&("trace"===r&&t?s:void 0!==console[r]?n(console,r):void 0!==console.log?n(console,"log"):e)}function c(a,t){for(var n=0;n<r.length;n++){var s=r[n];this[s]=n<a?e:this.methodFactory(s,a,t)}this.log=this.debug}function l(e,t,r){return function(){typeof console!==a&&(c.call(this,t,r),this[e].apply(this,arguments))}}function i(e,a,t){return o(e)||l.apply(this,arguments)}function u(e,t,n){var s,o=this,l="loglevel";function u(){var e;if(typeof window!==a&&l){try{e=window.localStorage[l]}catch(e){}if(typeof e===a)try{var t=window.document.cookie,r=t.indexOf(encodeURIComponent(l)+"=");-1!==r&&(e=/^([^;]+)/.exec(t.slice(r))[1])}catch(e){}return void 0===o.levels[e]&&(e=void 0),e}}"string"==typeof e?l+=":"+e:"symbol"==typeof e&&(l=void 0),o.name=e,o.levels={TRACE:0,DEBUG:1,INFO:2,WARN:3,ERROR:4,SILENT:5},o.methodFactory=n||i,o.getLevel=function(){return s},o.setLevel=function(t,n){if("string"==typeof t&&void 0!==o.levels[t.toUpperCase()]&&(t=o.levels[t.toUpperCase()]),!("number"==typeof t&&t>=0&&t<=o.levels.SILENT))throw"log.setLevel() called with invalid level: "+t;if(s=t,!1!==n&&function(e){var t=(r[e]||"silent").toUpperCase();if(typeof window!==a&&l){try{return void(window.localStorage[l]=t)}catch(e){}try{window.document.cookie=encodeURIComponent(l)+"="+t+";"}catch(e){}}}(t),c.call(o,t,e),typeof console===a&&t<o.levels.SILENT)return"No console available for logging"},o.setDefaultLevel=function(e){u()||o.setLevel(e,!1)},o.enableAll=function(e){o.setLevel(o.levels.TRACE,e)},o.disableAll=function(e){o.setLevel(o.levels.SILENT,e)};var d=u();null==d&&(d=null==t?"WARN":t),o.setLevel(d,!1)}var d=new u,v={};d.getLogger=function(e){if("symbol"!=typeof e&&"string"!=typeof e||""===e)throw new TypeError("You must supply a name when creating a logger.");var a=v[e];return a||(a=v[e]=new u(e,d.getLevel(),d.methodFactory)),a};var b=typeof window!==a?window.log:void 0;return d.noConflict=function(){return typeof window!==a&&window.log===d&&(window.log=b),d},d.getLoggers=function(){return v},d.default=d,d})?r.call(a,t,a,e):r)||(e.exports=n)}()}},a={};function t(r){var n=a[r];if(void 0!==n)return n.exports;var s=a[r]={exports:{}};return e[r].call(s.exports,s,s.exports,t),s.exports}t.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return t.d(a,{a}),a},t.d=(e,a)=>{for(var r in a)t.o(a,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},t.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a);var r={};return(()=>{"use strict";t.d(r,{DxfParser:()=>T});class e{constructor(e){this._pointer=0,this._eof=!1,this._data=e}next(){if(!this.hasNext())throw this._eof?new Error("Cannot call 'next' after EOF group has been read"):new Error("Unexpected end of input: EOF group not read before end of file. Ended on code "+this._data[this._pointer]);const e={code:parseInt(this._data[this._pointer])};return this._pointer++,e.value=a(e.code,this._data[this._pointer].trim()),this._pointer++,0===e.code&&"EOF"===e.value&&(this._eof=!0),this.lastReadGroup=e,e}peek(){if(!this.hasNext())throw this._eof?new Error("Cannot call 'next' after EOF group has been read"):new Error("Unexpected end of input: EOF group not read before end of file. Ended on code "+this._data[this._pointer]);const e={code:parseInt(this._data[this._pointer])};return e.value=a(e.code,this._data[this._pointer+1].trim()),e}rewind(e=1){this._pointer=this._pointer-2*e}hasNext(){return!(this._eof||this._pointer>this._data.length-2)}isEOF(){return this._eof}}function a(e,a){return e<=9?a:e>=10&&e<=59?parseFloat(a):e>=60&&e<=99?parseInt(a):e>=100&&e<=109?a:e>=110&&e<=149?parseFloat(a):e>=160&&e<=179?parseInt(a):e>=210&&e<=239?parseFloat(a):e>=270&&e<=289?parseInt(a):e>=290&&e<=299?function(e){if("0"===e)return!1;if("1"===e)return!0;throw TypeError("String '"+e+"' cannot be cast to Boolean type")}(a):e>=300&&e<=369?a:e>=370&&e<=389?parseInt(a):e>=390&&e<=399?a:e>=400&&e<=409?parseInt(a):e>=410&&e<=419?a:e>=420&&e<=429?parseInt(a):e>=430&&e<=439?a:e>=440&&e<=459?parseInt(a):e>=460&&e<=469?parseFloat(a):e>=470&&e<=481||999===eLine truncated
+1
View File
@@ -0,0 +1 @@
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).earcut={})}(this,function(t){"use strict";const n=new Set;let e=!1;function r(t,n,e,r,x){let o=null;if(x===C(t,n,e,r)>0)for(let x=n;x<e;x+=r)o=L(x/r|0,t[x],t[x+1],o);else for(let x=e-r;x>=n;x-=r)o=L(x/r|0,t[x],t[x+1],o);return o&&P(o,o.next)&&(q(o),o=o.next),o}function x(t,r=t){const x=r===t;let o,i=t;do{o=!1,i===i.next||0!==n.size&&n.has(i)||!P(i,i.next)&&0!==O(i.prev,i,i.next)?(x||i!==r)&&(i=i.next,o=!x):((x||i===r)&&(r=i.prev),e=!0,q(i),i=i.prev,o=!0)}while(o||i!==r);return r}function o(t,n,r,o,c){c&&function(t,n,e,r){let x=t,o=0;do{x.z=j(x.x,x.y,n,e,r),Z[o++]=x,x=x.next}while(x!==t);!function(t){if(t<=32){for(let n=1;n<t;n++){const t=Z[n],e=t.z;let r=n-1;for(;r>=0&&Z[r].z>e;)Z[r+1]=Z[r],r--;Z[r+1]=t}return}A.length<t&&(A=new Uint32Array(t),z=new Uint32Array(t),b=new Array(t));for(let n=0;n<t;n++)A[n]=Z[n].z;I(t,Z,A,b,z,0),I(t,b,z,Z,A,8),I(t,Z,A,b,z,16),I(t,b,z,Z,A,24)}(o);let i=null;for(let t=0;t<o;t++){const n=Z[t];n.prevZ=i,i&&(i.nextZ=n),i=n}i.nextZ=null,Z.fill(null,0,o),b.fill(null,0,o)}(t,r,o,c);let y=t,s=!1;for(;t.prev!==t.next;){const a=t.prev,h=t.next;if(O(a,t,h)<0&&(c?l(t,r,o,c):i(t)))n.push(a.i,t.i,h.i),q(t),t=h,y=h;else if((t=h)===y){if(e=!1,t=x(t),e){y=t;continue}if(!s){y=t=f(t,n),s=!0;continue}u(t,n,r,o,c);break}}}function i(t){const n=t.prev,e=t,r=t.next,x=n.x,o=e.x,i=r.x,l=n.y,f=e.y,u=r.y,c=Math.min(x,o,i),y=Math.min(l,f,u),s=Math.max(x,o,i),a=Math.max(l,f,u);let h=r.next;for(;h!==n;){if(h.x>=c&&h.x<=s&&h.y>=y&&h.y<=a&&(x!==h.x||l!==h.y)&&F(x,l,o,f,i,u,h.x,h.y)&&O(h.prev,h,h.next)>=0)return!1;h=h.next}return!0}function l(t,n,e,r){const x=t.prev,o=t,i=t.next,l=x.x,f=o.x,u=i.x,c=x.y,y=o.y,s=i.y,a=Math.min(l,f,u),h=Math.min(c,y,s),p=Math.max(l,f,u),v=Math.max(c,y,s),M=j(a,h,n,e,r),d=j(p,v,n,e,r);let g=t.prevZ;for(;g&&g.z>=M;){if(g.x>=a&&g.x<=p&&g.y>=h&&g.y<=v&&g!==i&&(l!==g.x||c!==g.y)&&F(l,c,f,y,u,s,g.x,g.y)&&O(g.prev,g,g.next)>=0)return!1;g=g.prevZ}let m=t.nextZ;for(;m&&m.z<=d;){if(m.x>=a&&m.x<=p&&m.y>=h&&m.y<=v&&m!==i&&(l!==m.x||c!==m.y)&&F(l,c,f,y,u,s,m.x,m.y)&&O(m.prev,m,m.next)>=0)return!1;m=m.nextZ}return!0}function f(t,n){let e=t,r=!1;do{const x=e.prev,o=e.next.next;S(x,e,e.next,o,!1)&&_(x,o)&&_(o,x)&&(n.push(x.i,e.i,o.i),q(e),q(e.next),e=t=o,r=!0),e=e.next}while(e!==t);return r?x(e):e}function u(t,n,e,r,i){let l=t;do{let t=l.next.next;for(;t!==l.prev;){if(l.i!==t.i&&N(l,t)){let f=E(l,t);return l=x(l,l.next),f=x(f,f.next),o(l,n,e,r,i),void o(f,n,e,r,i)}t=t.next}l=l.next}while(l!==t)}let c=!1;function y(t,n){return t.x-n.x||t.y-n.y||(t.next.y-t.y)/(t.next.x-t.x)-(n.next.y-n.y)/(n.next.x-n.x)}function s(t,n){const e=function(t,n){let e=n;const r=t.x,x=t.y;let o,i=-1/0;if(P(t,e))return e;for(let n=0,l=0;n<p;n++,l+=4){if(x<h[l+1]||x>h[l+3]||h[l]>r||h[l+2]<=i)continue;const f=g(n);e=m(n);do{if(e.prev.next===e){if(P(t,e.next))return e.next;if(x<=e.y&&x>=e.next.y&&e.next.y!==e.y){const t=e.x+(x-e.y)*(e.next.x-e.x)/(e.next.y-e.y);if(t<=r&&t>i&&(i=t,o=e.x<e.next.x?e:e.next,t===r))return o}}e=e.next}while(e!==f)}if(!o)return null;const l=o.x,f=o.y,u=Math.min(x,f),c=Math.max(x,f);let y=1/0;for(let n=0,s=0;n<p;n++,s+=4){if(h[s+2]<l||h[s]>r||h[s+3]<u||h[s+1]>c)continue;const a=g(n);e=m(n);do{if(e.prev.next===e&&r>=e.x&&e.x>=l&&r!==e.x&&F(x<f?r:i,x,l,f,x<f?i:r,x,e.x,e.y)){const n=Math.abs(x-e.y)/(r-e.x);(_(e,t)||e.y===x&&e.next.y===x&&e.next.x>r)&&(n<y||n===y&&(e.x>o.x||e.x===o.x&&w(o,e)))&&(o=e,y=n)}e=e.next}while(e!==a)}return o}(t,n);if(!e)return n;const r=E(e,t);return d(e,r.next.next),x(r,r.next),x(e,e.next)}const a=16;let h=new Float64Array(0),p=0;const v=[],M=[];function d(t,n){let e=t;do{const t=p++;v[t]=e;let r=e.x,x=e.y,o=e.x,i=e.y,l=0;do{const n=e.next;e.z=t,n.x<r&&(r=n.x),n.x>o&&(o=n.x),n.y<x&&(x=n.y),n.y>i&&(i=n.y),e=n}while(++l<a&&e!==n);M[t]=e;const f=4*t;h[f]=r,h[f+1]=x,h[f+2]=o,h[f+3]=i}while(e!==n)}function g(t){let n=M[t];for(;n.prev.next!==n;)n=n.next;return M[t]=n,n}function m(t){let n=v[t];for(;n.prev.next!==n;)n=n.next;return v[t]=n,n}function w(t,n){return O(t.prev,t,n.prev)<0&&O(n.next,t,t.next)<0}const Z=[];let b=[],A=new Uint32Array(0),z=new Uint32Array(0);const U=new Uint32Array(256);function I(t,n,e,r,x,o){U.fill(0);for(let n=0;n<t;n++)U[e[n]>>>o&255]++;let i=0;for(let t=0;t<256;t++){const n=U[t];U[t]=i,i+=n}for(let i=0;i<t;i++){const t=e[i],l=U[t>>>o&255]++;r[l]=n[i],x[l]=t}}function j(t,n,e,r,x){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-e)*x|0)|t<<8))|t<<4))|t<<2))|t<<1))|(n=1431655765&((n=858993459&((n=252645135&((n=16711935&((n=(n-r)*x|0)|n<<8))|n<<4))|n<<2))|n<<1))<<1}function k(t){let n=t,e=t;do{(n.x<e.x||n.x===e.x&&n.y<e.y)&&(e=n),n=n.next}while(n!==t);return e}function F(t,n,e,r,x,o,i,l){return(x-i)*(n-l)>=(t-i)*(o-l)&&(t-i)*(r-l)>=(e-i)*(n-l)&&(e-i)*(o-l)>=(x-i)*(r-l)}function N(t,n){const e=P(t,n)&&O(t.prev,t,t.next)>0&&O(n.prev,n,n.next)>0;Line truncated
+259
View File
@@ -0,0 +1,259 @@
<!doctype html>
<!--
============================================================================
프로젝트: 터널 단면 3D 파라메트릭 모델러 (Tunnel Modeler 3D)
개발자: m12205.tdc.hanmacgroup@gmail.com
저작권: Copyright (c) 2026 m12205.tdc.hanmacgroup@gmail.com. All rights reserved.
설명: DWG/DXF 표준단면도 기반 지능형 형상 인식 및 3D 솔리드 모델 자동 생성 웹 애플리케이션
============================================================================
-->
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>터널 단면 모델러 — DWG/DXF → 3D 모델</title>
<link rel="stylesheet" href="style.css">
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
<script src="dxf-parser.js"></script>
<script src="earcut.min.js"></script>
<script src="rhino3dm.min.js"></script>
<script defer src="app.js"></script>
</head>
<body>
<header class="topbar">
<div class="brand-mark">T</div>
<div>
<strong>터널 단면 모델러</strong>
<small>DWG 표준단면에서 10m 3D 모델 생성</small>
</div>
<div class="top-status">
<span class="status-dot" id="statusDot"></span>
<span id="engineStatus">엔진 확인 중</span>
</div>
</header>
<main class="layout">
<aside class="sidebar">
<section class="panel import-panel">
<div class="section-kicker">01 / 도면 입력</div>
<h1>표준단면 불러오기</h1>
<label id="dropzone" class="dropzone">
<input id="fileInput" type="file" accept=".dwg,.dxf">
<span class="upload-icon">↑</span>
<b>DWG 또는 DXF 파일 선택</b>
<small>파일은 이 PC 안에서 처리합니다</small>
</label>
<div id="fileMeta" class="file-meta hidden"></div>
<div class="sample-actions">
<button id="loadSample" class="quiet-button">예제 DXF 불러오기</button>
</div>
</section>
<section class="panel settings-panel">
<div class="section-kicker">02 / 모델 길이</div>
<label class="length-label" for="lengthInput">터널 모델 길이 <span>m</span></label>
<div class="length-input">
<input id="lengthInput" type="number" min="1" max="500" step="1" value="10">
<span>m</span>
</div>
<div class="length-presets">
<button type="button" class="preset-len-btn active" data-len="10">10m</button>
<button type="button" class="preset-len-btn" data-len="20">20m</button>
<button type="button" class="preset-len-btn" data-len="50">50m</button>
<button type="button" class="preset-len-btn" data-len="100">100m</button>
</div>
<p class="help">요청하신 기본값 10m로 설정되어 있습니다.</p>
<button id="buildButton" class="primary-button" disabled>단면 분석 및 3D 생성</button>
</section>
<section class="panel detection-panel">
<div class="section-kicker">03 / 구조물 인식</div>
<div class="detection-header">
<h2>인식 결과</h2>
<button type="button" id="toggleParamsBtn" class="quiet-button hidden" style="padding:0;">상세 제원 편집 ▾</button>
</div>
<div id="detectionList" class="detection-list">
<div class="empty-state">도면을 불러오면 자동 분석합니다.</div>
</div>
<!-- 추출된 파라미터 미세 조정 패널 (접기/펼치기 지원) -->
<div id="paramEditorPanel" class="param-editor hidden">
<div class="param-grid">
<label><span>천단반경 R1(mm)</span><input type="number" id="p_r1" value="5500"></label>
<label><span>중심각 θ1(°)</span><input type="number" id="p_theta1" value="120"></label>
<label><span>내공좌측 R2(mm)</span><input type="number" id="p_r2" value="7500"></label>
<label><span>내공우측 R3(mm)</span><input type="number" id="p_r3" value="7500"></label>
<label><span>외면좌측 R4(mm)</span><input type="number" id="p_r4" value="9200"></label>
<label><span>외면우측 R5(mm)</span><input type="number" id="p_r5" value="9200"></label>
<label><span>중심높이 H1(mm)</span><input type="number" id="p_h1" value="500"></label>
<label><span>이격거리 E(mm)</span><input type="number" id="p_offset" value="3500"></label>
<label><span>라이닝 두께(mm)</span><input type="number" id="p_lining_t" value="300"></label>
<label><span>숏크리트 두께(mm)</span><input type="number" id="p_shotcrete_t" value="150"></label>
<label><span>편경사(%)</span><input type="number" id="p_se" step="0.1" value="-2.0"></label>
<label><span>포장 표층 t1(mm)</span><input type="number" id="p_pave_t1" value="20"></label>
<label><span>포장 콘크리트 t2</span><input type="number" id="p_pave_t2" value="260"></label>
<label><span>포장 필터층 t3</span><input type="number" id="p_pave_t3" value="150"></label>
</div>
<button type="button" id="applyParamsBtn" class="secondary-button">변경 제원으로 3D 갱신</button>
</div>
<p class="note">
레이어 이름에 의존하지 않고 아치의 동심 관계, 폐합 윤곽, 객체의 위치와 치수로 단면 요소를 찾습니다.
라이닝과 하부 배수콘크리트는 경계면 없는 단일 일체형 솔리드로 생성되며, 공동구는 내부를 비우고 받침턱 위에 길이 방향 뚜껑을 설치합니다.
</p>
</section>
<div class="sidebar-foot">공동구 내부는 비우고 도면의 받침턱 위에 뚜껑을 얹습니다.</div>
</aside>
<section class="workspace">
<div class="view-tabs">
<button class="view-tab active" data-view="3d">3D 모델</button>
<button class="view-tab" data-view="2d">2D 단면</button>
<div class="view-spacer"></div>
<div class="viewer-toolbar">
<div id="opacitySliderBox" class="opacity-slider-box" title="3D 모델 객체 투명도 조절: 100% 완전 솔리드 ~ 10% 내부 투시">
<span class="slider-label">객체 투명도</span>
<input type="range" id="opacitySlider" min="10" max="100" value="100" step="5" class="slider-range">
<span id="opacityVal" class="slider-val">100%</span>
</div>
<button type="button" id="btnViewInside" class="tool-button" title="터널 내부 주행 시점">👁️ 내부 시점</button>
<button type="button" id="btnViewBird" class="tool-button" title="전체 조감도 시점">조감도 시점</button>
<div class="theme-switch">
<button type="button" id="btnThemeDark" class="theme-btn active">🌙 다크</button>
<button type="button" id="btnThemeLight" class="theme-btn">☀️ 라이트</button>
</div>
<button id="resetView" class="icon-button" title="화면 초기화">⟲</button>
<button id="exportObj" class="export-button" disabled>OBJ 내보내기</button>
<button id="export3dm" class="export-button" disabled title="Rhino 3D 모델 파일(.3dm)로 내보내기">3DM 내보내기</button>
</div>
</div>
<div id="viewport" class="viewport">
<!-- 3D Plotly 뷰어 -->
<div id="placement_3d_plot" class="plot-3d"></div>
<!-- 2D 단면 캔버스 -->
<canvas id="modelCanvas" class="canvas-2d hidden"></canvas>
<!-- 초기 안내 화면 -->
<div id="welcome" class="welcome">
<div class="welcome-glyph">◈</div>
<h2>단면 도면을 불러오세요</h2>
<p>인식된 표준단면을 길이 10m로 생성해 3D로 확인합니다.</p>
</div>
<!-- 캔버스 내부 우측 상단 플로팅 오버레이: 전구 표시 세로 리스트 -->
<div id="placement_3d_layer_panel" class="layer-panel">
<!-- [전체 마스터 토글 행] -->
<div class="layer-master-row">
<div onclick="toggleAll3DObjects()" title="전체 모델 한번에 켜기/끄기" class="layer-master-left">
<span id="bulb_icon_all" class="bulb-icon master-bulb">💡</span>
<span id="label_layer_all" class="master-label">전체 모델</span>
</div>
<div class="layer-master-right">
<span onclick="toggleAll3DObjects()" id="badge_status_all" class="status-badge is-on">ON</span>
<button onclick="toggle3DLayerPanelMinimize()" id="btn_toggle_layer_panel" title="목록 접기/펼치기" class="minimize-btn">−</button>
</div>
</div>
<!-- 모델별 세로 리스트 영역 (접기 대상) -->
<div id="placement_3d_layer_items_wrapper" class="layer-items-wrapper">
<div class="layer-divider"></div>
<!-- 1. 숏크리트 -->
<div onclick="toggle3DObject('shotcrete')" id="item_layer_shotcrete" class="layer-item">
<div class="layer-item-left">
<span id="bulb_icon_shotcrete" class="bulb-icon">💡</span>
<span class="color-dot" style="background:#475569; border-color:rgba(255,255,255,0.4);"></span>
<span id="label_layer_shotcrete" class="layer-label">숏크리트</span>
</div>
<span id="badge_status_shotcrete" class="status-badge is-on">ON</span>
</div>
<!-- 2. 라이닝·공동구 -->
<div onclick="toggle3DObject('lining')" id="item_layer_lining" class="layer-item">
<div class="layer-item-left">
<span id="bulb_icon_lining" class="bulb-icon">💡</span>
<span class="color-dot" style="background:#8da0b0; border-color:rgba(255,255,255,0.4);"></span>
<span id="label_layer_lining" class="layer-label">라이닝·공동구</span>
</div>
<span id="badge_status_lining" class="status-badge is-on">ON</span>
</div>
<!-- 3. 도로포장 -->
<div onclick="toggle3DObject('pavement')" id="item_layer_pavement" class="layer-item">
<div class="layer-item-left">
<span id="bulb_icon_pavement" class="bulb-icon">💡</span>
<span class="color-dot" style="background:#1e293b; border-color:#475569;"></span>
<span id="label_layer_pavement" class="layer-label">도로포장</span>
</div>
<span id="badge_status_pavement" class="status-badge is-on">ON</span>
</div>
<!-- 4. 공동구 뚜껑 -->
<div onclick="toggle3DObject('lids')" id="item_layer_lids" class="layer-item">
<div class="layer-item-left">
<span id="bulb_icon_lids" class="bulb-icon">💡</span>
<span class="color-dot" style="background:#cbd5e1; border-color:rgba(255,255,255,0.4);"></span>
<span id="label_layer_lids" class="layer-label">공동구 뚜껑</span>
</div>
<span id="badge_status_lids" class="status-badge is-on">ON</span>
</div>
<!-- 5. 맹암거 -->
<div onclick="toggle3DObject('blindDitch')" id="item_layer_blindDitch" class="layer-item">
<div class="layer-item-left">
<span id="bulb_icon_blindDitch" class="bulb-icon">💡</span>
<span class="color-dot" style="background:#d97706; border-color:rgba(255,255,255,0.4);"></span>
<span id="label_layer_blindDitch" class="layer-label">맹암거</span>
</div>
<span id="badge_status_blindDitch" class="status-badge is-on">ON</span>
</div>
<!-- 6. 종배수관 -->
<div onclick="toggle3DObject('drainPipe')" id="item_layer_drainPipe" class="layer-item">
<div class="layer-item-left">
<span id="bulb_icon_drainPipe" class="bulb-icon">💡</span>
<span class="color-dot" style="background:#ea580c; border-color:rgba(255,255,255,0.4);"></span>
<span id="label_layer_drainPipe" class="layer-label">종배수관</span>
</div>
<span id="badge_status_drainPipe" class="status-badge is-on">ON</span>
</div>
<!-- 7. 차선 -->
<div onclick="toggle3DObject('lanes')" id="item_layer_lanes" class="layer-item">
<div class="layer-item-left">
<span id="bulb_icon_lanes" class="bulb-icon">💡</span>
<span class="color-dot" style="background:#ffffff; border-color:rgba(255,255,255,0.4);"></span>
<span id="label_layer_lanes" class="layer-label">차선</span>
</div>
<span id="badge_status_lanes" class="status-badge is-on">ON</span>
</div>
<!-- 8. 단면 테두리 -->
<div onclick="toggle3DObject('rims')" id="item_layer_rims" class="layer-item">
<div class="layer-item-left">
<span id="bulb_icon_rims" class="bulb-icon">💡</span>
<span class="color-dot" style="background:#10b981; border-color:rgba(255,255,255,0.4);"></span>
<span id="label_layer_rims" class="layer-label">단면 테두리</span>
</div>
<span id="badge_status_rims" class="status-badge is-on">ON</span>
</div>
</div>
</div>
<div id="hint" class="view-hint hidden">왼쪽 드래그: 회전 · 오른쪽 드래그: 이동 · 휠: 확대/축소</div>
<div class="canvas-credit" id="canvasCredit">Developed by m12205.tdc.hanmacgroup@gmail.com · © 2026</div>
</div>
<div class="bottom-bar">
<span id="modelSummary">도면 대기 중</span>
<span class="developer-credit">Developed by m12205.tdc.hanmacgroup@gmail.com · © 2026</span>
<span id="cursorInfo">단위: 도면 mm / 3D 모델 m</span>
</div>
</section>
</main>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
var rhino3dm=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var Module=moduleArg,moduleArg=!!globalThis.window,ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope,ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&"renderer"!=globalThis.process?.type,thisProgram="./this.program",scriptDirectory=("undefined"!=typeof __filename?_scriptName=__filename:ENVIRONMENT_IS_WORKER&&(_scriptName=self.location.href),"");if(ENVIRONMENT_IS_NODE){var fs=require("node:fs"),scriptDirectory=__dirname+"/",readBinary=filename=>(filename=isFileURI(filename)?new URL(filename):filename,fs.readFileSync(filename)),readAsync=async(filename,binary=!0)=>(filename=isFileURI(filename)?new URL(filename):filename,fs.readFileSync(filename,binary?void 0:"utf8"));1<process.argv.length&&(thisProgram=process.argv[1].replace(/\\/g,"/")),process.argv.slice(2),0}else if(moduleArg||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}ENVIRONMENT_IS_WORKER&&(readBinary=url=>{var xhr=new XMLHttpRequest;return xhr.open("GET",url,!1),xhr.responseType="arraybuffer",xhr.send(null),new Uint8Array(xhr.response)}),readAsync=async url=>{if(isFileURI(url))return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,!0),xhr.responseType="arraybuffer",xhr.onload=()=>{200==xhr.status||0==xhr.status&&xhr.response?resolve(xhr.response):reject(xhr.status)},xhr.onerror=reject,xhr.send(null)});var response=await fetch(url,{credentials:"same-origin"});if(response.ok)return response.arrayBuffer();throw new Error(response.status+" : "+response.url)}}var wasmBinary,readyPromiseResolve,readyPromiseReject,out=console.log.bind(console),err=console.error.bind(console),ABORT=!1,isFileURI=filename=>filename.startsWith("file://");var wasmBinaryFile,HEAP16,HEAP32,HEAP64,HEAP8,HEAPF32,HEAPF64,HEAPU16,HEAPU32,HEAPU64,HEAPU8,runtimeInitialized=!1;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b),HEAP16=new Int16Array(b),HEAPU8=new Uint8Array(b),HEAPU16=new Uint16Array(b),HEAP32=new Int32Array(b),HEAPU32=new Uint32Array(b),HEAPF32=new Float32Array(b),HEAPF64=new Float64Array(b),HEAP64=new BigInt64Array(b),HEAPU64=new BigUint64Array(b)}function abort(what){Module.onAbort?.(what),err(what=`Aborted(${what})`),ABORT=!0,what+=". Build with -sASSERTIONS for more info.";what=new WebAssembly.RuntimeError(what);throw readyPromiseReject?.(what),what}function findWasmBinary(){return path="rhino3dm.wasm",Module.locateFile?Module.locateFile(path,scriptDirectory):scriptDirectory+path;var path}async function getWasmBinary(binaryFile){if(!wasmBinary)try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}response=binaryFile;if(response==wasmBinaryFile&&wasmBinary)return new Uint8Array(wasmBinary);if(readBinary)return readBinary(response);throw"both async and sync fetching of the wasm failed"}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE)try{var response=fetch(binaryFile,{credentials:"same-origin"});return await WebAssembly.instantiateStreaming(response,imports)}catch(reason){err("wasm streaming compile failed: "+reason),err("falling back to ArrayBuffer instantiation")}return(async(binaryFile,imports)=>{try{var binary=await getWasmBinary(binaryFile);return await WebAssembly.instantiate(binary,imports)}catch(reason){err("failed to asynchronously prepare wasm: "+reason),abort(reason)}})(binaryFile,imports)}var callRuntimeCallbacks=callbacks=>{for(;0<callbacks.length;)callbacks.shift()(Module)},onPostRuns=[],addOnPostRun=cb=>onPostRuns.push(cb),onPreRuns=[],addOnPreRun=cb=>onPreRuns.push(cb);class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr,this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){HEAP8[this.ptr+12]=caught=caught?1:0}get_caught(){return 0!=HEAP8[this.ptr+12]}set_rethrown(rethrown){HEAP8[this.ptr+13]=rethrown=rethrown?1:0}get_rethrown(){return 0!=HEAP8[this.ptr+13]}init(type,destructor){this.set_adjusted_ptr(0),this.set_type(type),this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var initRandomFill=()=>{var nodeCrypto;return ENVIRONMENT_IS_NODE?(nodeCrypto=require("node:crypto"),view=>nodeCrypto.randomFillSync(view)):view=>(crypto.getRandomValues(view),0)},randomFill=view=>(randomFill=initRandomFill())(view),PATH={isAbs:path=>"/"===path.charAt(0),splitPath:filename=>/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(filename).slice(1),normalizeArray:(parts,allowAboveRoot)=>{for(var up=0,i=parts.length-1;0<=i;i--){var last=parts[i];"."===last?parts.splice(i,1):".."===last?(parts.splice(i,1),up++):up&&(parts.splice(i,1),up--)}if(allowAboveRoot)for(;up;up--)parts.unshifLine truncated
`,dtorStack=(needsDestructorStack&&(invokerFnBody+="var destructors = [];\n"),needsDestructorStack?"destructors":"null"),args1=["humanName","throwBindingError","invoker","fn","runDestructors","fromRetWire","toClassParamWire"];for(isClassMethodFunc&&(invokerFnBody+=`var thisWired = toClassParamWire(${dtorStack}, this);
`),i=0;i<argCount;++i){var argName=`toArg${i}Wire`;invokerFnBody+=`var arg${i}Wired = ${argName}(${dtorStack}, arg${i});
`,args1.push(argName)}if(invokerFnBody+=(returns||isAsync?"var rv = ":"")+`invoker(${argsListWired});
`,needsDestructorStack)invokerFnBody+="runDestructors(destructors);\n";else for(i=isClassMethodFunc?1:2;i<argTypes.length;++i){var paramName=1===i?"thisWired":"arg"+(i-2)+"Wired";null!==argTypes[i].destructorFunction&&(invokerFnBody+=paramName+`_dtor(${paramName});
`,args1.push(paramName+"_dtor"))}return returns&&(invokerFnBody+="var ret = fromRetWire(rv);\nreturn ret;\n"),invokerFnBody+="}\n",new Function(args1,invokerFnBody)})(argTypes,classType,returns,isAsync)(...closureArgs);return createNamedFunction(humanName,cppInvokerFunc)}var heap32VectorToArray=(count,firstElement)=>{for(var array=[],i=0;i<count;i++)array.push(HEAPU32[firstElement+4*i>>2]);return array},getFunctionName=signature=>{var argsIndex=(signature=signature.trim()).indexOf("(");return-1===argsIndex?signature:signature.slice(0,argsIndex)},validateThis=(this_,classType,humanName)=>(this_ instanceof Object||throwBindingError(humanName+' with invalid "this": '+this_),this_ instanceof classType.registeredClass.constructor||throwBindingError(humanName+' incompatible with "this" of type '+this_.constructor.name),this_.$$.ptr||throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`),upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)),emval_freelist=[],emval_handles=[0,1,,1,null,1,!0,1,!1,1],__emval_decref=handle=>{9<handle&&0==--emval_handles[handle+1]&&(emval_handles[handle],emval_handles[handle]=void 0,emval_freelist.push(handle))},Emval={toValue:handle=>(handle||throwBindingError("Cannot use deleted val. handle = "+handle),emval_handles[handle]),toHandle:value=>{switch(value){case void 0:return 2;case null:return 4;case!0:return 6;case!1:return 8;default:var handle=emval_freelist.pop()||emval_handles.length;return emval_handles[handle]=value,emval_handles[handle+1]=1,handle}}},EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);return __emval_decref(handle),rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null},enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): `+name)}};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];return void 0===impl&&throwBindingError(humanName+" has unknown type "+getTypeName(rawType)),impl},installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{proto[Symbol.iterator]||(proto[Symbol.iterator]=function(){return((size,getValue)=>{let index=0;return{next(){var current;return index>=size?{done:!0}:(current=index,index++,{value:getValue(current),done:!1})},[Symbol.iterator](){return this}}})(this[sizeMethodName](),i=>this[getMethodName](i))})},EmValOptionalType=Object.assign({optional:!0},EmValType),stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite),UTF16Decoder=new TextDecoder("utf-16le"),UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{ptr>>=1,maxBytesToRead=findStringEnd(HEAPU16,ptr,maxBytesToRead/2,ignoreNul);return UTF16Decoder.decode(HEAPU16.subarray(ptr,maxBytesToRead))},stringToUTF16=(str,outPtr,maxBytesToWrite)=>{if((maxBytesToWrite??=2147483647)<2)return 0;for(var startPtr=outPtr,numCharsToWrite=(maxBytesToWrite-=2)<2*str.length?maxBytesToWrite/2:str.length,i=0;i<numCharsToWrite;++i){var codeUnit=str.charCodeAt(i);HEAP16[outPtr>>1]=codeUnit,outPtr+=2}return HEAP16[outPtr>>1]=0,outPtr-startPtr},lengthBytesUTF16=str=>2*str.length,UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{for(var str="",startIdx=ptr>>2,i=0;!(maxBytesToRead/4<=i);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str},stringToUTF32=(str,outPtr,maxBytesToWrite)=>{if((maxBytesToWrite??=2147483647)<4)return 0;for(var startPtr=outPtr,endPtr=startPtr+maxBytesToWrite-4,i=0;i<str.length;++i){var codePoint=str.codePointAt(i);if(65535<codePoint&&i++,HEAP32[outPtr>>2]=codePoint,endPtr<(outPtr+=4)+4)break}return HEAP32[outPtr>>2]=0,outPtr-startPtr},lengthBytesUTF32=str=>{for(var len=0,i=0;i<str.length;++i)65535<str.codePointAt(i)&&i++,len+=4;return len},emval_methodCallers=[],emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[],toReturnWire=toReturnWire(destructors,handle);return destructors.length&&(HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)),toReturnWire},emval_symbols={},getStringOrSymbol=address=>{var symbol=emval_symbols[address];return void 0===symbol?AsciiToString(address):symbol};var isLeapYear=year=>year%4==0&&(year%100!=0||year%400==0),MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335],MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334],ydayFromDate=date=>(isLeapYear(date.getFullYear())?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_ Line truncated
${functionBody}
}`,argFromPtr=new Function(Object.keys(captures),functionBody)(...Object.values(captures)),kind=`methodCaller<(${argTypes.map(t=>t.name)}) => ${argTypesPtr.name}>`;return argCount=createNamedFunction(kind,argFromPtr),toReturnWire=emval_methodCallers.length,emval_methodCallers.push(argCount),toReturnWire},na:__emval_decref,C:(first,second)=>(first=Emval.toValue(first))==(second=Emval.toValue(second)),s:name=>name?(name=getStringOrSymbol(name),Emval.toHandle(globalThis[name])):Emval.toHandle(globalThis),B:name=>(name=getStringOrSymbol(name),Emval.toHandle(Module[name])),u:(handle,key)=>(handle=Emval.toValue(handle),key=Emval.toValue(key),Emval.toHandle(handle[key])),J:handle=>{9<handle&&(emval_handles[handle+1]+=1)},A:(object,constructor)=>(object=Emval.toValue(object))instanceof(constructor=Emval.toValue(constructor)),g:(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args),ja:handle=>"number"==typeof(handle=Emval.toValue(handle)),ia:handle=>"string"==typeof(handle=Emval.toValue(handle)),l:()=>Emval.toHandle([]),q:v=>Emval.toHandle(getStringOrSymbol(v)),j:()=>Emval.toHandle({}),W:handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors),__emval_decref(handle)},r:(handle,key,value)=>{handle=Emval.toValue(handle),key=Emval.toValue(key),value=Emval.toValue(value),handle[key]=value},T:function(time,tmPtr){time=bigintToI53Checked(time);var time=new Date(1e3*time),start=(HEAP32[tmPtr>>2]=time.getUTCSeconds(),HEAP32[tmPtr+4>>2]=time.getUTCMinutes(),HEAP32[tmPtr+8>>2]=time.getUTCHours(),HEAP32[tmPtr+12>>2]=time.getUTCDate(),HEAP32[tmPtr+16>>2]=time.getUTCMonth(),HEAP32[tmPtr+20>>2]=time.getUTCFullYear()-1900,HEAP32[tmPtr+24>>2]=time.getUTCDay(),Date.UTC(time.getUTCFullYear(),0,1,0,0,0,0)),time=(time.getTime()-start)/864e5|0;HEAP32[tmPtr+28>>2]=time},U:function(time,tmPtr){time=bigintToI53Checked(time);var time=new Date(1e3*time),yday=(HEAP32[tmPtr>>2]=time.getSeconds(),HEAP32[tmPtr+4>>2]=time.getMinutes(),HEAP32[tmPtr+8>>2]=time.getHours(),HEAP32[tmPtr+12>>2]=time.getDate(),HEAP32[tmPtr+16>>2]=time.getMonth(),HEAP32[tmPtr+20>>2]=time.getFullYear()-1900,HEAP32[tmPtr+24>>2]=time.getDay(),0|ydayFromDate(time)),yday=(HEAP32[tmPtr+28>>2]=yday,HEAP32[tmPtr+36>>2]=-60*time.getTimezoneOffset(),new Date(time.getFullYear(),0,1)),summerOffset=new Date(time.getFullYear(),6,1).getTimezoneOffset(),yday=yday.getTimezoneOffset(),time=0|(summerOffset!=yday&&time.getTimezoneOffset()==Math.min(yday,summerOffset));HEAP32[tmPtr+32>>2]=time},V:function(tmPtr){date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var guessedOffset,start,summerOffset,dst,date,dstOffset=isNaN(date.getTime())?-1:(dst=HEAP32[tmPtr+32>>2],guessedOffset=date.getTimezoneOffset(),start=new Date(date.getFullYear(),0,1),summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset(),start=start.getTimezoneOffset(),dstOffset=Math.min(start,summerOffset),dst<0?HEAP32[tmPtr+32>>2]=Number(summerOffset!=start&&dstOffset==guessedOffset):0<dst!=(dstOffset==guessedOffset)&&(start=Math.max(start,summerOffset),summerOffset=0<dst?dstOffset:start,date.setTime(date.getTime()+6e4*(summerOffset-guessedOffset))),HEAP32[tmPtr+24>>2]=date.getDay(),dst=0|ydayFromDate(date),HEAP32[tmPtr+28>>2]=dst,HEAP32[tmPtr>>2]=date.getSeconds(),HEAP32[tmPtr+4>>2]=date.getMinutes(),HEAP32[tmPtr+8>>2]=date.getHours(),HEAP32[tmPtr+12>>2]=date.getDate(),HEAP32[tmPtr+16>>2]=date.getMonth(),HEAP32[tmPtr+20>>2]=date.getYear(),date.getTime()/1e3);return BigInt(dstOffset)},X:(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear(),winter=new Date(currentYear,0,1),currentYear=new Date(currentYear,6,1),winter=winter.getTimezoneOffset(),currentYear=currentYear.getTimezoneOffset(),stdTimezoneOffset=Math.max(winter,currentYear),timezone=(HEAPU32[timezone>>2]=60*stdTimezoneOffset,HEAP32[daylight>>2]=Number(winter!=currentYear),timezoneOffset=>{var sign=0<=timezoneOffset?"-":"+",timezoneOffset=Math.abs(timezoneOffset);return"UTC"+sign+String(Math.floor(timezoneOffset/60)).padStart(2,"0")+String(timezoneOffset%60).padStart(2,"0")}),stdTimezoneOffset=timezone(winter),daylight=timezone(currentYear);currentYear<winter?(stringToUTF8(stdTimezoneOffset,std_name,17),stringToUTF8(daylight,dst_name,17)):(stringToUTF8(stdTimezoneOffset,dst_name,17),stringToUTF8(daylight,std_name,17))},H:()=>Date.now(),F:()=>performance.now(),Q:requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;if(!(2147483648<requestedSize))for(var alignment,cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown),overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);if((size=>{size=(size-wasmMemory.buffer.byteLength+65535)/65536|0;try{return wasmMemory.grow(size),updateMemoryViews(),1}catch(e){}})(Math.min(2147483648,(overGrownHeapSize=Math.max(requestedSize,overGrownHeapSize),alignment=65536,Math.ceil(overGrownHeapSize/alignm Line truncated