216 lines
8.0 KiB
Python
216 lines
8.0 KiB
Python
import os
|
|
import json
|
|
import tempfile
|
|
from flask import Flask, request, jsonify, send_file, Response
|
|
from flask_cors import CORS
|
|
|
|
import cad_parser
|
|
import model_3d
|
|
import exporters
|
|
|
|
app = Flask(__name__, static_folder='static', static_url_path='/static')
|
|
CORS(app, resources={r"/*": {"origins": "*"}})
|
|
|
|
# In-memory active session state (starts empty until user imports drawings in Step 1)
|
|
current_drawings_list = []
|
|
current_drawing_data = None
|
|
current_model_data = None
|
|
|
|
|
|
@app.route('/')
|
|
def index():
|
|
return send_file('index.html')
|
|
|
|
|
|
@app.route('/api/reset', methods=['POST'])
|
|
def api_reset():
|
|
global current_drawings_list, current_drawing_data, current_model_data
|
|
current_drawings_list = []
|
|
current_drawing_data = None
|
|
current_model_data = None
|
|
return jsonify({'status': 'success'})
|
|
|
|
|
|
@app.route('/api/default-drawing', methods=['GET'])
|
|
def api_default_drawing():
|
|
return jsonify({
|
|
'status': 'success',
|
|
'drawing': current_drawing_data,
|
|
'drawings': current_drawings_list,
|
|
'model': current_model_data
|
|
})
|
|
|
|
|
|
@app.route('/api/project-drawings', methods=['GET'])
|
|
def api_project_drawings():
|
|
return jsonify({
|
|
'status': 'success',
|
|
'drawings': current_drawings_list,
|
|
'drawing': current_drawing_data,
|
|
'model': current_model_data
|
|
})
|
|
|
|
|
|
@app.route('/api/classify', methods=['POST'])
|
|
def api_classify():
|
|
"""
|
|
1.2 도면 종류 판별 API:
|
|
도면명 키워드 자동 판별 결과 반환 및 수동 선택 업데이트 지원
|
|
"""
|
|
global current_drawings_list, current_model_data
|
|
body = request.get_json() or {}
|
|
drawing_name = body.get('drawingName', '')
|
|
manual_type = body.get('manualType') # Optional: 'GENERAL' or 'REBAR'
|
|
|
|
res = cad_parser.classify_drawing_by_name(drawing_name)
|
|
selected_type = manual_type if manual_type in ('GENERAL', 'REBAR') else res['auto_type']
|
|
label_map = {
|
|
'GENERAL': '일반도',
|
|
'REBAR': '배근도/구조도',
|
|
'UNCLASSIFIED': '미분류 (수동 선택 필요)'
|
|
}
|
|
|
|
classification_obj = {
|
|
'autoType': res['auto_type'],
|
|
'autoLabel': res['auto_label'],
|
|
'matchedKeyword': res['matched_keyword'],
|
|
'reason': res['reason'],
|
|
'needsManualSelection': res['needs_manual_selection'],
|
|
'selectedType': selected_type,
|
|
'selectedLabel': label_map.get(selected_type, res['auto_label']),
|
|
'isManualOverride': bool(manual_type and manual_type != res['auto_type'])
|
|
}
|
|
|
|
# Update active drawing classification in memory and regenerate 3D model
|
|
for d in current_drawings_list:
|
|
if d.get('fileName') == drawing_name or d.get('id') == drawing_name:
|
|
d['classification'] = classification_obj
|
|
|
|
if current_drawings_list:
|
|
current_model_data = model_3d.generate_full_model(drawings_list=current_drawings_list)
|
|
|
|
return jsonify({
|
|
'status': 'success',
|
|
'classification': classification_obj,
|
|
'model': current_model_data
|
|
})
|
|
|
|
|
|
@app.route('/api/generate-3d', methods=['POST'])
|
|
def api_generate_3d():
|
|
global current_model_data, current_drawings_list
|
|
params = request.get_json() or {}
|
|
model_res = model_3d.generate_full_model(params=params, drawings_list=current_drawings_list)
|
|
current_model_data = model_res
|
|
return jsonify({
|
|
'status': 'success',
|
|
'model': model_res
|
|
})
|
|
|
|
|
|
@app.route('/api/upload', methods=['POST'])
|
|
def api_upload():
|
|
files = request.files.getlist('files')
|
|
if not files and 'file' in request.files:
|
|
files = [request.files['file']]
|
|
|
|
if not files or all(f.filename == '' for f in files):
|
|
return jsonify({'status': 'error', 'message': '업로드된 도면 파일이 없습니다.'}), 400
|
|
|
|
reset_flag = request.form.get('reset', 'false').lower() == 'true'
|
|
|
|
temp_dir = tempfile.gettempdir()
|
|
saved_files = []
|
|
|
|
# 1. Save all uploaded files first so companion sheets ((01)<->(02), (03)<->(04)) can cross-reference each other
|
|
for file in files:
|
|
filename = os.path.basename(file.filename)
|
|
ext = os.path.splitext(filename)[1].lower()
|
|
if ext not in ('.dwg', '.dxf'):
|
|
return jsonify({'status': 'error', 'message': f'지원하지 않는 파일 형식입니다 ({filename}). .dwg, .dxf만 지원합니다.'}), 400
|
|
|
|
temp_path = os.path.join(temp_dir, filename)
|
|
file.save(temp_path)
|
|
saved_files.append((filename, temp_path))
|
|
|
|
# 2. Parse each uploaded DWG file
|
|
parsed_drawings = []
|
|
for filename, temp_path in saved_files:
|
|
try:
|
|
drawing_data = cad_parser.parse_dwg(temp_path, custom_filename=filename)
|
|
parsed_drawings.append(drawing_data)
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': f'도면 분석 중 오류 발생 ({filename}): {str(e)}'}), 500
|
|
|
|
global current_drawing_data, current_model_data, current_drawings_list
|
|
if reset_flag or current_drawings_list is None:
|
|
current_drawings_list = []
|
|
|
|
# If the user uploads a drawing from a DIFFERENT station (e.g. switching from STA.13+611.21 to STA.0+000.00),
|
|
# automatically replace drawings from old stations so old sample drawings don't pollute the new model.
|
|
new_stations = set()
|
|
for pd in parsed_drawings:
|
|
st = (pd.get('structuralParams') or {}).get('station') or (pd.get('rebarSpecs') or {}).get('station')
|
|
if st:
|
|
new_stations.add(st)
|
|
|
|
if new_stations and current_drawings_list:
|
|
filtered_existing = []
|
|
for ed in current_drawings_list:
|
|
ed_st = (ed.get('structuralParams') or {}).get('station') or (ed.get('rebarSpecs') or {}).get('station')
|
|
if not ed_st or ed_st in new_stations:
|
|
filtered_existing.append(ed)
|
|
current_drawings_list = filtered_existing
|
|
|
|
existing_by_name = {d['fileName']: i for i, d in enumerate(current_drawings_list)}
|
|
for pd in parsed_drawings:
|
|
if pd['fileName'] in existing_by_name:
|
|
current_drawings_list[existing_by_name[pd['fileName']]] = pd
|
|
else:
|
|
current_drawings_list.append(pd)
|
|
|
|
current_drawing_data = parsed_drawings[0]
|
|
current_model_data = model_3d.generate_full_model(drawings_list=current_drawings_list)
|
|
|
|
return jsonify({
|
|
'status': 'success',
|
|
'drawing': current_drawing_data,
|
|
'uploadedDrawings': parsed_drawings,
|
|
'drawings': current_drawings_list,
|
|
'model': current_model_data
|
|
})
|
|
|
|
|
|
@app.route('/api/export/<fmt>', methods=['GET', 'POST'])
|
|
def api_export(fmt):
|
|
global current_model_data, current_drawings_list
|
|
model = current_model_data
|
|
if request.is_json:
|
|
custom_params = request.get_json()
|
|
model = model_3d.generate_full_model(params=custom_params, drawings_list=current_drawings_list)
|
|
elif model is None:
|
|
model = model_3d.generate_full_model(drawings_list=current_drawings_list)
|
|
|
|
fmt = fmt.lower()
|
|
if fmt == 'obj':
|
|
content = exporters.export_obj(model, include_rebars=True)
|
|
return Response(content, mimetype='text/plain', headers={'Content-Disposition': 'attachment; filename=wingwall_model.obj'})
|
|
elif fmt == 'stl':
|
|
content = exporters.export_stl(model)
|
|
return Response(content, mimetype='application/sla', headers={'Content-Disposition': 'attachment; filename=wingwall_model.stl'})
|
|
elif fmt == 'dxf':
|
|
content = exporters.export_dxf_3d(model, include_rebars=True)
|
|
return Response(content, mimetype='application/dxf', headers={'Content-Disposition': 'attachment; filename=wingwall_model_3d.dxf'})
|
|
elif fmt == 'csv':
|
|
content = exporters.export_rebar_bom_csv(model)
|
|
return Response(content, mimetype='text/csv; charset=utf-8', headers={'Content-Disposition': 'attachment; filename=wingwall_rebar_bom.csv'})
|
|
elif fmt == 'json':
|
|
return jsonify(model)
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Unsupported format'}), 400
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print("Starting Wingwall 3D & Rebar Modeler Server at http://127.0.0.1:5000")
|
|
app.run(host='127.0.0.1', port=5000, debug=False)
|