137 lines
6.2 KiB
JavaScript
137 lines
6.2 KiB
JavaScript
/**
|
|
* ============================================================================
|
|
* 프로젝트: 터널 단면 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(`====================================================`);
|
|
});
|