# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. import base64 import io import logging from odoo import http from odoo.http import request _logger = logging.getLogger(__name__) class FpConfiguratorController(http.Controller): @http.route('/fp/3d-viewer', type='http', auth='user', website=False) def viewer_3d(self, **kw): """Serve the standalone 3D viewer HTML page. Query params: id (attachment ID), name (filename for format detection). The HTML page loads Online3DViewer and renders the model. """ from odoo.modules.module import get_module_path import os mod_path = get_module_path('fusion_plating_configurator') html_path = os.path.join( mod_path, 'static', 'src', 'html', '3d_viewer.html', ) with open(html_path, 'r', encoding='utf-8') as f: content = f.read() return request.make_response(content, headers=[ ('Content-Type', 'text/html; charset=utf-8'), ]) @http.route('/fp/3d-model//', type='http', auth='user', website=False) def serve_3d_model(self, attachment_id, filename, **kw): """Serve a 3D model file from ir.attachment. This bypasses the /web/content auth issues when loading inside an iframe. The filename in the URL ensures Online3DViewer can detect the format from the extension. """ attachment = request.env['ir.attachment'].browse(attachment_id) if not attachment.exists(): return request.not_found() raw = base64.b64decode(attachment.datas) # Map common CAD extensions to MIME types mime_map = { '.step': 'application/step', '.stp': 'application/step', '.iges': 'application/iges', '.igs': 'application/iges', '.stl': 'application/sla', '.brep': 'application/octet-stream', '.brp': 'application/octet-stream', '.obj': 'text/plain', '.gltf': 'model/gltf+json', '.glb': 'model/gltf-binary', } import os ext = os.path.splitext(filename)[1].lower() content_type = mime_map.get(ext, 'application/octet-stream') return request.make_response(raw, headers=[ ('Content-Type', content_type), ('Content-Disposition', f'inline; filename="{filename}"'), ('Content-Length', str(len(raw))), ]) @http.route('/fp/configurator/calculate_surface_area', type='jsonrpc', auth='user') def calculate_surface_area(self, attachment_id, **kw): """Calculate surface area from an uploaded STL file using trimesh.""" attachment = request.env['ir.attachment'].browse(int(attachment_id)) if not attachment.exists(): return {'error': 'Attachment not found.'} try: import trimesh except ImportError: return {'error': 'trimesh library not installed. Run: pip install trimesh'} try: raw = base64.b64decode(attachment.datas) mesh = trimesh.load(io.BytesIO(raw), file_type='stl') # trimesh returns area in the file's native units (usually mm²) area_mm2 = mesh.area area_sqin = area_mm2 / 645.16 # mm² to sq in return { 'surface_area': round(area_sqin, 4), 'surface_area_mm2': round(area_mm2, 2), 'unit': 'sq_in', 'vertex_count': len(mesh.vertices), 'face_count': len(mesh.faces), 'bounding_box': { 'x': round(float(mesh.bounding_box.extents[0]), 2), 'y': round(float(mesh.bounding_box.extents[1]), 2), 'z': round(float(mesh.bounding_box.extents[2]), 2), }, } except Exception as e: _logger.warning('STL surface area calculation failed: %s', e) return {'error': str(e)}