From 795c66c12615e2aa5cdc0320c4c45bb0e99f0d05 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 12 Apr 2026 20:53:54 -0400 Subject: [PATCH] feat(configurator): server-side surface area calculation from STL Add trimesh-based surface area calculation for uploaded STL files: - New /fp/configurator/calculate_surface_area jsonrpc endpoint - action_calculate_surface_area() method on fp.part.catalog - "Calculate from 3D Model" button visible when a 3D model is attached - Returns area in sq in (converted from mm2), vertex/face counts, and bounding box dimensions Co-Authored-By: Claude Opus 4.6 (1M context) --- .../fusion_plating_configurator/__init__.py | 1 + .../controllers/__init__.py | 2 + .../controllers/configurator_controller.py | 52 +++++++++++++++++++ .../models/fp_part_catalog.py | 38 +++++++++++++- .../views/fp_part_catalog_views.xml | 9 +++- 5 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 fusion-plating/fusion_plating_configurator/controllers/__init__.py create mode 100644 fusion-plating/fusion_plating_configurator/controllers/configurator_controller.py diff --git a/fusion-plating/fusion_plating_configurator/__init__.py b/fusion-plating/fusion_plating_configurator/__init__.py index 3c90fa80..2ea9535e 100644 --- a/fusion-plating/fusion_plating_configurator/__init__.py +++ b/fusion-plating/fusion_plating_configurator/__init__.py @@ -3,4 +3,5 @@ # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. +from . import controllers from . import models diff --git a/fusion-plating/fusion_plating_configurator/controllers/__init__.py b/fusion-plating/fusion_plating_configurator/controllers/__init__.py new file mode 100644 index 00000000..034d6501 --- /dev/null +++ b/fusion-plating/fusion_plating_configurator/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import configurator_controller diff --git a/fusion-plating/fusion_plating_configurator/controllers/configurator_controller.py b/fusion-plating/fusion_plating_configurator/controllers/configurator_controller.py new file mode 100644 index 00000000..f5044de0 --- /dev/null +++ b/fusion-plating/fusion_plating_configurator/controllers/configurator_controller.py @@ -0,0 +1,52 @@ +# -*- 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/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)} diff --git a/fusion-plating/fusion_plating_configurator/models/fp_part_catalog.py b/fusion-plating/fusion_plating_configurator/models/fp_part_catalog.py index ec62df29..f4e89a27 100644 --- a/fusion-plating/fusion_plating_configurator/models/fp_part_catalog.py +++ b/fusion-plating/fusion_plating_configurator/models/fp_part_catalog.py @@ -3,7 +3,7 @@ # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. -from odoo import fields, models +from odoo import api, fields, models, _ class FpPartCatalog(models.Model): @@ -63,3 +63,39 @@ class FpPartCatalog(models.Model): ('fp_part_catalog_partner_partnum_uniq', 'unique(partner_id, part_number)', 'Part number must be unique per customer.'), ] + + def action_calculate_surface_area(self): + """Calculate surface area from the uploaded 3D model file.""" + self.ensure_one() + if not self.model_attachment_id: + from odoo.exceptions import UserError + raise UserError(_('No 3D model file uploaded.')) + + try: + import trimesh + except ImportError: + from odoo.exceptions import UserError + raise UserError(_('trimesh library not installed on the server. Contact your administrator.')) + + import base64 + import io + + raw = base64.b64decode(self.model_attachment_id.datas) + mesh = trimesh.load(io.BytesIO(raw), file_type='stl') + area_mm2 = mesh.area + area_sqin = area_mm2 / 645.16 + + self.surface_area = round(area_sqin, 4) + self.surface_area_uom = 'sq_in' + self.geometry_source = '3d_model' + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('Surface Area Calculated'), + 'message': _('%.4f sq in (%.2f mm\u00b2) from %d faces') % (area_sqin, area_mm2, len(mesh.faces)), + 'type': 'success', + 'sticky': False, + }, + } diff --git a/fusion-plating/fusion_plating_configurator/views/fp_part_catalog_views.xml b/fusion-plating/fusion_plating_configurator/views/fp_part_catalog_views.xml index 80ac6e2a..f85db347 100644 --- a/fusion-plating/fusion_plating_configurator/views/fp_part_catalog_views.xml +++ b/fusion-plating/fusion_plating_configurator/views/fp_part_catalog_views.xml @@ -44,7 +44,14 @@ - +