터널 단면 3D 모델러 최신화: 남한산성/방아다리 터널 설계 제원 정밀 반영 및 UI/서버 파일 추가
This commit is contained in:
1 parent
3340359817
commit
e4d1978df5
15 files changed
+1840007
-47
No files matched your search
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* 프로젝트: 터널 단면 3D 파라메트릭 모델러 (Tunnel Modeler 3D)
|
||||
* 개발자: m12205.tdc.hanmacgroup@gmail.com
|
||||
* 저작권: Copyright (c) 2026 m12205.tdc.hanmacgroup@gmail.com. All rights reserved.
|
||||
* 설명: DWG/DXF 변환 및 정적 웹 서비스 로컬 서버
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
const PORT = Number(process.env.PORT || 4180);
|
||||
const ROOT = __dirname;
|
||||
const PARENT_ROOT = path.resolve(__dirname, '..');
|
||||
|
||||
function findConverter() {
|
||||
const candidates = [
|
||||
process.env.ODA_FILE_CONVERTER,
|
||||
'C:\\Program Files\\ODA\\ODAFileConverter\\ODAFileConverter.exe',
|
||||
'C:\\Program Files\\ODA\\ODAFileConverter 27.9.0\\ODAFileConverter.exe',
|
||||
'C:\\Program Files\\ODAFileConverter\\ODAFileConverter.exe',
|
||||
'C:\\Program Files\\Open Design Alliance\\ODAFileConverter.exe'
|
||||
].filter(Boolean);
|
||||
for (const candidate of candidates) if (fs.existsSync(candidate)) return candidate;
|
||||
for (const root of [process.env.ProgramFiles, process.env['ProgramFiles(x86)']].filter(Boolean)) {
|
||||
for (const folder of ['ODA', 'ODA File Converter', 'Open Design Alliance']) {
|
||||
const parent = path.join(root, folder);
|
||||
if (!fs.existsSync(parent)) continue;
|
||||
for (const entry of fs.readdirSync(parent, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory() || !/oda.*converter/i.test(entry.name)) continue;
|
||||
const candidate = path.join(parent, entry.name, 'ODAFileConverter.exe');
|
||||
if (fs.existsSync(candidate)) return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function send(res, status, body, type = 'application/json; charset=utf-8') {
|
||||
res.writeHead(status, { 'Content-Type': type, 'Cache-Control': 'no-store' });
|
||||
res.end(typeof body === 'string' ? body : JSON.stringify(body));
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
||||
|
||||
if (req.method === 'GET' && url.pathname === '/api/status') {
|
||||
const converter = findConverter();
|
||||
return send(res, 200, { converterAvailable: Boolean(converter), converterPath: converter });
|
||||
}
|
||||
|
||||
if (req.method === 'GET' && url.pathname === '/api/sample') {
|
||||
const sampleCandidates = [
|
||||
path.join(ROOT, 'sample.dxf'),
|
||||
path.join(PARENT_ROOT, 'sejong_seoun1.dxf'),
|
||||
path.join(PARENT_ROOT, 'Tunnel3D', 'sejong_seoun1.dxf'),
|
||||
path.join(PARENT_ROOT, 'Tunnel3D-inspect', 'out', 'source.dxf')
|
||||
];
|
||||
const sample = sampleCandidates.find(p => fs.existsSync(p));
|
||||
if (!sample) return send(res, 404, { error: '예제 DXF 파일을 찾지 못했습니다.' });
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-store' });
|
||||
return fs.createReadStream(sample).pipe(res);
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && url.pathname === '/api/convert') {
|
||||
const converter = findConverter();
|
||||
if (!converter) {
|
||||
return send(res, 503, { error: 'ODA File Converter를 찾지 못했습니다. DXF 파일을 사용하거나 ODA File Converter를 설치해 주세요.' });
|
||||
}
|
||||
const original = path.basename(decodeURIComponent(req.headers['x-filename'] || 'drawing.dwg'));
|
||||
if (!/\.dwg$/i.test(original)) {
|
||||
return send(res, 400, { error: 'DWG 파일만 변환할 수 있습니다.' });
|
||||
}
|
||||
const work = fs.mkdtempSync(path.join(os.tmpdir(), 'tunnel-modeler-3d-'));
|
||||
const input = path.join(work, 'in');
|
||||
const output = path.join(work, 'out');
|
||||
fs.mkdirSync(input);
|
||||
fs.mkdirSync(output);
|
||||
const source = path.join(input, 'source.dwg');
|
||||
const outFile = path.join(output, 'source.dxf');
|
||||
const stream = fs.createWriteStream(source);
|
||||
req.pipe(stream);
|
||||
stream.on('error', () => {
|
||||
fs.rmSync(work, { recursive: true, force: true });
|
||||
send(res, 500, { error: 'DWG 파일을 임시 작업 폴더에 저장하지 못했습니다.' });
|
||||
});
|
||||
stream.on('finish', () => {
|
||||
const child = spawn(converter, [input, output, 'ACAD2018', 'DXF', '0', '0', '*.DWG'], { windowsHide: true });
|
||||
let stderr = '';
|
||||
child.stderr.on('data', data => { stderr += data.toString(); });
|
||||
child.on('error', error => {
|
||||
fs.rmSync(work, { recursive: true, force: true });
|
||||
send(res, 500, { error: `DWG 변환기를 실행하지 못했습니다: ${error.message}` });
|
||||
});
|
||||
child.on('close', code => {
|
||||
if (!fs.existsSync(outFile)) {
|
||||
fs.rmSync(work, { recursive: true, force: true });
|
||||
return send(res, 422, { error: `DXF 변환 결과가 없습니다 (종료 코드 ${code}). ${stderr.slice(0, 500)}` });
|
||||
}
|
||||
const dxf = fs.readFileSync(outFile, 'utf8');
|
||||
fs.rmSync(work, { recursive: true, force: true });
|
||||
send(res, 200, dxf, 'text/plain; charset=utf-8');
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method !== 'GET') return send(res, 405, { error: '허용되지 않은 요청입니다.' });
|
||||
|
||||
const relative = url.pathname === '/' ? 'index.html' : decodeURIComponent(url.pathname).replace(/^\/+/, '');
|
||||
const file = path.resolve(ROOT, relative);
|
||||
if (!file.startsWith(ROOT + path.sep) || !fs.existsSync(file) || fs.statSync(file).isDirectory()) {
|
||||
return send(res, 404, '파일을 찾을 수 없습니다.', 'text/plain; charset=utf-8');
|
||||
}
|
||||
const types = {
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.js': 'text/javascript; charset=utf-8',
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.json': 'application/json; charset=utf-8',
|
||||
'.dxf': 'text/plain; charset=utf-8',
|
||||
'.wasm': 'application/wasm'
|
||||
};
|
||||
res.writeHead(200, { 'Content-Type': types[path.extname(file).toLowerCase()] || 'application/octet-stream', 'Cache-Control': 'no-store' });
|
||||
fs.createReadStream(file).pipe(res);
|
||||
});
|
||||
|
||||
server.listen(PORT, '127.0.0.1', () => {
|
||||
console.log(`====================================================`);
|
||||
console.log(` 터널 단면 3D 모델러 실행 중: http://127.0.0.1:${PORT}`);
|
||||
console.log(` ODA File Converter: ${findConverter() ? '준비됨 (DWG 직독 가능)' : '미설치 (DXF 사용 가능)'}`);
|
||||
console.log(`====================================================`);
|
||||
});
|
||||
Reference in new issue
Block a user