Files
Odoo-Modules/fusion-plating/fusion_plating_configurator/controllers/configurator_controller.py
gsinghpal 0ff8c0b93f changes
2026-04-13 02:35:35 -04:00

102 lines
4.0 KiB
Python

# -*- 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/<int:attachment_id>/<string:filename>',
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)}