From 9df1bcfc2a15324e8aad85641f4c0f4011be5e70 Mon Sep 17 00:00:00 2001 From: lek Date: Tue, 27 Oct 2026 11:01:12 +0900 Subject: [PATCH] =?UTF-8?q?feat:=201=EC=B0=A8=20=EA=B3=BC=EC=A0=9C=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++++- enable-network.ps1 | 24 ++++++++++++++++++++++++ git-log.txt | 26 ++++++++++++++++++++++++++ index.html | 7 +++++++ launch-viewer.ps1 | 41 +++++++++++++++++++++++++++++++++++++++++ r2c.py | 9 ++++++--- tests/test_r2c.py | 12 ++++++++++-- web/viewer.html | 6 ++++++ web/viewer.js | 1 + 뷰어 실행 (2).zip | Bin 0 -> 310 bytes 뷰어 실행.cmd | 3 +++ 뷰어 실행.zip | Bin 0 -> 310 bytes 12 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 enable-network.ps1 create mode 100644 git-log.txt create mode 100644 launch-viewer.ps1 create mode 100644 뷰어 실행 (2).zip create mode 100644 뷰어 실행.cmd create mode 100644 뷰어 실행.zip diff --git a/README.md b/README.md index af81367..fa0d76f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ ## 실행 및 로컬 웹 게시 +Windows 탐색기에서는 프로젝트 폴더의 **`뷰어 실행.cmd`를 더블클릭**하세요. WSL 서버가 꺼져 있으면 시작하고, 응답을 확인한 다음 브라우저를 엽니다. 서버 창은 사용하는 동안 열어 두세요. `index.html` 자체는 서버를 시작하지 않으므로 클릭 실행에는 `뷰어 실행.cmd`를 사용하세요. + WSL 터미널에서 다음 명령을 실행합니다. ```bash @@ -11,7 +13,9 @@ cd /home/b19203/3d-drawing-web python3 r2c.py serve ``` -브라우저에서 **http://localhost:8000** 을 엽니다. 서버 종료는 `Ctrl+C`입니다. 실행할 때마다 DXF에서 모델과 웹페이지를 다시 생성합니다. 생성된 루트 `index.html`을 직접 열어 기본 도면의 규격별 모델을 볼 수도 있습니다. **DXF 파일 첨부 변환은 `serve`로 실행한 웹사이트에서 사용합니다.** 프로그램을 수정한 경우 기존 서버를 종료하고 다시 실행하세요. +브라우저에서 **http://localhost:8000** 을 엽니다. 서버 종료는 `Ctrl+C`입니다. 실행할 때마다 DXF에서 모델과 웹페이지를 다시 생성합니다. 생성된 루트 `index.html`을 더블클릭해도 서버 뷰어로 자동 이동합니다. **DXF 파일 첨부 변환은 `serve`로 실행한 웹사이트에서 사용합니다.** 프로그램을 수정한 경우 기존 서버를 종료하고 다시 실행하세요. + +HTML 파일을 더블클릭하면 `file://`에서 `http://localhost:8000/`로 자동 이동합니다. `--port`를 지정해 빌드하거나 서버를 실행하면 해당 포트로 이동하도록 생성됩니다. HTTP로 접속한 페이지에서는 이동하지 않습니다. HTML은 서버 프로그램을 직접 시작할 수 없으므로 먼저 위 명령으로 서버를 실행하세요. 다른 포트 또는 로컬 네트워크에 게시하려면 다음처럼 실행합니다. diff --git a/enable-network.ps1 b/enable-network.ps1 new file mode 100644 index 0000000..0e24808 --- /dev/null +++ b/enable-network.ps1 @@ -0,0 +1,24 @@ +param( + [Parameter(Mandatory=$true)][string]$WslAddress, + [Parameter(Mandatory=$true)][string]$ListenAddress +) +$ErrorActionPreference = 'Stop' +$log = Join-Path $env:TEMP 'r2c-network-setup.log' +try { + $wslIp = [System.Net.IPAddress]::Parse($WslAddress) + $listenIp = [System.Net.IPAddress]::Parse($ListenAddress) + if ($wslIp.AddressFamily -ne 'InterNetwork' -or $listenIp.AddressFamily -ne 'InterNetwork') { + throw 'IPv4 addresses are required.' + } + & netsh.exe interface portproxy add v4tov4 listenaddress=$ListenAddress listenport=8000 connectaddress=$WslAddress connectport=8000 + if ($LASTEXITCODE -ne 0) { throw 'Could not configure port forwarding.' } + $ruleName = 'R2C-Viewer-8000-Private10' + if (Get-NetFirewallRule -Name $ruleName -ErrorAction SilentlyContinue) { + Remove-NetFirewallRule -Name $ruleName + } + New-NetFirewallRule -Name $ruleName -DisplayName 'R2C viewer (10.0.0.0/8)' -Direction Inbound -Action Allow -Protocol TCP -LocalPort 8000 -LocalAddress $ListenAddress -RemoteAddress '10.0.0.0/8' -Profile Any | Out-Null + "OK: http://${ListenAddress}:8000/ -> ${WslAddress}:8000; allowed clients: 10.0.0.0/8" | Set-Content $log +} catch { + $_.Exception.Message | Set-Content $log + exit 1 +} diff --git a/git-log.txt b/git-log.txt new file mode 100644 index 0000000..f994952 --- /dev/null +++ b/git-log.txt @@ -0,0 +1,26 @@ +Git 커밋 기록 +작성 시간: 2026-10-23 09:29:16+09:00 + +commit 9cee9bf79126f9d87220dd45d6a6038611d750a7 (HEAD -> master) +Author: lek +Date: Thu Oct 22 11:28:16 2026 + + Validate DXF geometry and splines and improve viewer maintenance + +commit 11fd3badf6f6e46d20f010ffa151c46fe87dc7ad +Author: lek +Date: Tue Oct 20 21:23:57 2026 + + Build DXF-based R2C 3D viewer and local web server + +commit 277c7005aba2c7b8fb15ca3268516c1a687765bc +Author: lek +Date: Tue Oct 20 20:45:56 2026 + + Add R2C drawing 3D viewer + +commit b59196feb3bf98764f118b13c52fdf3a3f2fadff +Author: lek +Date: Tue Oct 20 12:06:14 2026 + + Start 3D practice diff --git a/index.html b/index.html index b10241f..aca2344 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,11 @@ R2C · DXF 도면 3D 뷰어 +
+
R2
R2C 도면 스튜디오DXF → 단면 재구성 → 웹 3D
DXF 엔티티 추출원본 DXF ↓
DRAWING / RECONSTRUCTION

사각 입구에서 원형 관로까지

R2C관의 전환부와 소켓을 원본 단면으로 확인하세요.

모든 치수는 mm 기준
원본 A-A / B-B 단면 및 변화치수표 반영
DXF 파일을 첨부하거나 여기에 놓으세요

R2C 일반도의 단면·변화치수표를 읽어 바로 3D로 변환합니다. · 최대 10 MB

첨부 파일은 변환 후 서버에 저장하지 않습니다.

@@ -34,6 +40,7 @@ let sourceDocument=initialDocument,data=initialDocument,selectedDiameter=initial const initialDrawing=document.querySelector('.drawing-viewport').innerHTML,initialSource=document.getElementById('sourceLink').getAttribute('href'); let sourceUrl=null,uploadBusy=false; const specification=document.getElementById('specification'); +document.getElementById('localFileNotice').hidden=location.protocol!=='file:'; const tableKeys=['D','D1','D2','L','L1','L2','L3','B','b1','b2','b3','b4','H','h1','h2','h3','h4','h5','h6','h7']; let model='r2c',mode='closed',yaw=.87,pitch=.40,zoom=1,wire=false,drag=null,pending=false; const fmt=n=>Math.round(n).toLocaleString('ko-KR'); diff --git a/launch-viewer.ps1 b/launch-viewer.ps1 new file mode 100644 index 0000000..efb0aa6 --- /dev/null +++ b/launch-viewer.ps1 @@ -0,0 +1,41 @@ +$ErrorActionPreference = 'Stop' +$url = 'http://127.0.0.1:8000/' +$serverOutput = Join-Path $env:TEMP 'r2c-viewer-server.log' +$serverError = Join-Path $env:TEMP 'r2c-viewer-server-error.log' + +function Test-Viewer { + try { + $model = Invoke-RestMethod -Uri ($url + 'model.json') -TimeoutSec 10 + return ($null -ne $model.meshes -and $null -ne $model.variants) + } catch { return $false } +} + +try { + if (-not (Test-Viewer)) { + # Explorer exposes WSL folders as either of these UNC paths. + if ($PSScriptRoot -match '^\\\\(?:wsl\$|wsl\.localhost)\\([^\\]+)\\(.*)$') { + $distro = $Matches[1] + $linuxPath = '/' + $Matches[2].Replace('\', '/') + $wslArgs = @('-d', $distro, '--cd', ('"' + $linuxPath + '"'), '--', 'python3', 'r2c.py', 'serve') + } else { + $linuxPath = & wsl.exe -- wslpath -u $PSScriptRoot + if ($LASTEXITCODE -ne 0) { throw 'Cannot locate the project in WSL.' } + $wslArgs = @('--cd', ('"' + $linuxPath.Trim() + '"'), '--', 'python3', 'r2c.py', 'serve') + } + $server = Start-Process -FilePath 'wsl.exe' -ArgumentList $wslArgs -PassThru -RedirectStandardOutput $serverOutput -RedirectStandardError $serverError + $ready = $false + for ($attempt = 0; $attempt -lt 30; $attempt++) { + if (Test-Viewer) { $ready = $true; break } + if ($server.HasExited) { + $details = Get-Content $serverError -Raw -ErrorAction SilentlyContinue + throw ('The viewer server exited: ' + $details) + } + Start-Sleep -Milliseconds 500 + } + if (-not $ready) { throw 'The viewer did not respond at http://localhost:8000/. Check WSL and port 8000.' } + } + Start-Process $url +} catch { + Write-Host $_.Exception.Message -ForegroundColor Red + exit 1 +} diff --git a/r2c.py b/r2c.py index 9c0583e..991c8f5 100644 --- a/r2c.py +++ b/r2c.py @@ -516,7 +516,9 @@ def initial_scene(model): return ''.join(result) -def build(dxf=DEFAULT_DXF, output=ROOT / 'dist', update_entry=True): +def build(dxf=DEFAULT_DXF, output=ROOT / 'dist', update_entry=True, port=8000): + if not isinstance(port, int) or not 1 <= port <= 65535: + raise ValueError('포트는 1~65535 사이의 정수여야 합니다.') dxf, output = Path(dxf).resolve(), Path(output).resolve() if output == ROOT or output == ROOT / 'drawing': raise ValueError('출력 폴더는 dist 같은 별도 폴더로 지정해 주세요.') @@ -524,6 +526,7 @@ def build(dxf=DEFAULT_DXF, output=ROOT / 'dist', update_entry=True): model, svg = payload['model'], payload['drawingSvg'] output.mkdir(parents=True, exist_ok=True) template = (ROOT / 'web' / 'viewer.html').read_text(encoding='utf-8') + template = template.replace('__SERVER_PORT__', str(port)).replace('http://localhost:8000/', f'http://localhost:{port}/') template = template.replace('__VIEWER_JS__', (ROOT / 'web' / 'viewer.js').read_text(encoding='utf-8')) template = template.replace('__INITIAL_SCENE__', initial_scene(model)).replace('__DXF_HREF__', './drawing/' + html.escape(dxf.name, quote=True)) page = template.replace('__MODEL_JSON__', json.dumps(model, ensure_ascii=False, separators=(',', ':')).replace('<', '\\u003c')).replace('__DRAWING_SVG__', svg) @@ -545,11 +548,11 @@ def main(): parser.add_argument('command', choices=['build', 'serve'], nargs='?', default='serve') parser.add_argument('--dxf', type=Path, default=DEFAULT_DXF) parser.add_argument('--output', type=Path, default=ROOT / 'dist') - parser.add_argument('--host', default='127.0.0.1', help='다른 기기에서 접속하려면 0.0.0.0') + parser.add_argument('--host', default='0.0.0.0', help='수신 주소 (기본: 0.0.0.0, 로컬 전용: 127.0.0.1)') parser.add_argument('--port', type=int, default=8000) args = parser.parse_args() try: - build(args.dxf, args.output) + build(args.dxf, args.output, port=args.port) if args.command == 'serve': handler = functools.partial(ViewerHandler, directory=str(args.output.resolve())) with http.server.ThreadingHTTPServer((args.host, args.port), handler) as server: diff --git a/tests/test_r2c.py b/tests/test_r2c.py index 43cbcef..507e58b 100644 --- a/tests/test_r2c.py +++ b/tests/test_r2c.py @@ -80,12 +80,20 @@ class DrawingReconstructionTests(unittest.TestCase): self.assertNotIn('__DRAWING_SVG__', source) self.assertNotIn('__VIEWER_JS__', source) self.assertIn('function drawingRegions()', source) - before_script = source.split('', 1)[0] + self.assertGreater(scene.count(' R2C · DXF 도면 3D 뷰어 +
+
R2
R2C 도면 스튜디오DXF → 단면 재구성 → 웹 3D
DXF 엔티티 추출원본 DXF ↓
DRAWING / RECONSTRUCTION

사각 입구에서 원형 관로까지

R2C관의 전환부와 소켓을 원본 단면으로 확인하세요.

모든 치수는 mm 기준
원본 A-A / B-B 단면 및 변화치수표 반영
DXF 파일을 첨부하거나 여기에 놓으세요

R2C 일반도의 단면·변화치수표를 읽어 바로 3D로 변환합니다. · 최대 10 MB

첨부 파일은 변환 후 서버에 저장하지 않습니다.

diff --git a/web/viewer.js b/web/viewer.js index 818bff9..66af757 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -6,6 +6,7 @@ let sourceDocument=initialDocument,data=initialDocument,selectedDiameter=initial const initialDrawing=document.querySelector('.drawing-viewport').innerHTML,initialSource=document.getElementById('sourceLink').getAttribute('href'); let sourceUrl=null,uploadBusy=false; const specification=document.getElementById('specification'); +document.getElementById('localFileNotice').hidden=location.protocol!=='file:'; const tableKeys=['D','D1','D2','L','L1','L2','L3','B','b1','b2','b3','b4','H','h1','h2','h3','h4','h5','h6','h7']; let model='r2c',mode='closed',yaw=.87,pitch=.40,zoom=1,wire=false,drag=null,pending=false; const fmt=n=>Math.round(n).toLocaleString('ko-KR'); diff --git a/뷰어 실행 (2).zip b/뷰어 실행 (2).zip new file mode 100644 index 0000000000000000000000000000000000000000..74e923ee5175e69ca5378f049c670bb5179ba57b GIT binary patch literal 310 zcmWIWW@Zs#;Nak3&~%87Wk3Rg3)hb?PPOrj%B2GcdA%RDp@m z5MBoM{kQLD9Ry+!Eqcb0>yUwf+rwHvLy?$eH>5ijc)t{N`s2*jc5Z^<*1HwouH9|s zzsI{%D^v5}m+ohxO3G#dH81aM*)ZqljS0WoOJ{~!yf3-0yEC}vU+IoN_sXVTnI7}_ zl!gA{pC&ANE!VbihH?0&th7GF7~sv$VSh)mG7IRILLd(CW@Hj!MtB8TPLM$Y)hb?PPOrj%B2GcdA%RDp@m z5MBoM{kQLD9Ry+!Eqcb0>yUwf+rwHvLy?$eH>5ijc)t{N`s2*jc5Z^<*1HwouH9|s zzsI{%D^v5}m+ohxO3G#dH81aM*)ZqljS0WoOJ{~!yf3-0yEC}vU+IoN_sXVTnI7}_ zl!gA{pC&ANE!VbihH?0&th7GF7~sv$VSh)mG7IRILLd(CW@Hj!MtB8TPLM$Y