import logging import base64 import hashlib import os import platform import json from datetime import datetime import requests from odoo import http from odoo.http import request # from OCC.Extend.DataExchange import read_step_file # from OCC.Extend.DataExchange import write_stl_file from odoo import models, fields, api from odoo.modules import get_resource_path from odoo.exceptions import ValidationError, UserError from odoo.addons.sf_base.commons.common import Common from . import parser_and_calculate_work_time as pc class QuickEasyOrder(models.Model): _name = 'quick.easy.order' _description = '简易下单' _order = 'id desc' name = fields.Char('订单编号', default=lambda self: self.env['ir.sequence'].next_by_code('quick.easy.order')) model_length = fields.Float('长(mm)', digits=(16, 3)) model_width = fields.Float('宽(mm)', digits=(16, 3)) model_height = fields.Float('高(mm)', digits=(16, 3)) model_volume = fields.Float('体积(mm³)', digits=(16, 3)) model_processing_side = fields.Char('加工面', default='A') model_feature = fields.Char('特征') machining_precision = fields.Selection([ ('0.10', '±0.10mm'), ('0.05', '±0.05mm'), ('0.03', '±0.03mm'), ('0.02', '±0.02mm'), ('0.01', '±0.01mm')], string='加工精度', default='0.10') material_id = fields.Many2one('sf.production.materials', '材料') material_model_id = fields.Many2one('sf.materials.model', '型号') # process_id = fields.Many2one('sf.production.process', string='表面工艺') parameter_ids = fields.Many2many('sf.production.process.parameter', 'process_item_order_rel', string='可选参数') quantity = fields.Integer('数量', default=1) unit_price = fields.Float('单价') price = fields.Float('总价') model_file = fields.Binary('glb模型文件') upload_model_file = fields.Many2many('ir.attachment', 'upload_qf_model_file_attachment_ref', string='上传模型文件') delivery_time = fields.Date('交货日期') customer_id = fields.Many2one('res.partner', string='客户', default=lambda self: self.env.user.partner_id.id) state = fields.Selection([('草稿', '草稿'), ('待派单', '待派单'), ('待接单', '待接单'), ('加工中', '加工中'), ('物流中', '物流中'), ('已交付', '已交付')], string='订单状态', default='草稿', readonly=True) model_color_state = fields.Selection([ ('success', '成功'), ('fail', '失败')], string='模型上色状态') processing_time = fields.Integer('加工时长(min)') @api.depends('unit_price', 'quantity') def _compute_total_amount(self): for item in self: item.price = item.unit_price * item.quantity @api.depends('material_id', 'material_model_id') def _compute_material_model(self): for item in self: materials = self.env['sf.production.materials'].search([], limit=1, order='id desc') item.material_id = materials.id item.material_model_id = self.env['sf.materials.model'].search( [('materials_id', '=', materials.id)], limit=1, order='id desc') @api.model def create(self, vals): if vals.get('upload_model_file'): logging.info('create-attachment:%s' % vals['upload_model_file'][0]) for item in vals['upload_model_file']: print(len(item[2])) if len(item[2]) > 0: logging.info('create-attachment:%s' % int(item[2][0])) attachment = self.env['ir.attachment'].sudo().search([('id', '=', int(item[2][0]))]) # base64_data = base64.b64encode(attachment.datas) # base64_datas = base64_data.decode('utf-8') # model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() # report_path = attachment._full_path(attachment.store_fname) vals['model_file'] = self.model_analyze(attachment) # logging.info('create-model_file:%s' % len(vals['model_file'])) obj = super(QuickEasyOrder, self).create(vals) # self.model_coloring(obj) logging.info('---------开始派单到工厂-------') self.distribute_to_factory(obj) obj.state = '待接单' return obj def model_analyze(self,model_attachment): """ step模型解析,上传模型时转为web可显示的格式 :return: """ config = request.env['res.config.settings'].sudo().get_values() try: # 获取当前操作系统 os_name = platform.system() for item in model_attachment: # 将拿到的3D模型数据存入文件 # 定义文件名和文件的二进制内容 file_name = item.name # 请将这里替换为你的文件名 print('file_name', file_name) # base64_data = base64.b64encode(item.datas) # base64_datas = base64_data.decode('utf-8') binary_content = item.datas # 请将这里替换为你的文件的二进制内容 # binary_content从字符串转为二进制 binary_content = base64.b64decode(binary_content) # 定义新的文件夹路径 # 根据操作系统不同,文件路径不同 path_header = '/model_parser' if os_name == 'Linux' else 'D:/model_analysis' # new_folder_path = 'D:/11111final' + '/' + item['name'].split(".")[0] new_folder_path = path_header + '/' + item.name.rpartition('.')[0] print('new_folder_path', new_folder_path) # 检查新的文件夹是否存在,如果不存在,则创建 if not os.path.exists(new_folder_path): os.makedirs(new_folder_path) # 定义新的文件路径 new_file_path = os.path.join(new_folder_path, file_name) # 将二进制内容写入新的文件 with open(new_file_path, 'wb') as f: f.write(binary_content) # 检查文件是否已经成功写入 if os.path.exists(new_file_path): print(f'Successfully wrote binary content to {new_file_path}') else: print(f'Failed to write binary content to {new_file_path}') # 附件 # attachment = request.env['ir.attachment'].sudo().create({ # 'datas': item['data'].encode('utf-8'), # 'type': 'binary', # 'description': '模型文件', # 'name': item['name'], # 'public': True, # 'model_name': item['name'], # }) headers = {'Content-Type': 'application/json'} # 调用写入宿主机接口 # url_dir = 'http://192.168.50.202:8000/create_and_write_file' url_dir = config['model_parser_url'] + '/create_and_write_file' data = { 'folder_path': new_folder_path, # 您想要创建的文件夹路径 'file_path': new_file_path, # 您想要创建的文件名 'content': item['data'] # 您想要写入文件的内容 } requests.post(url_dir, json=data, headers=headers) # 调用特征包接口 url = config['model_parser_url'] + '/process_file' payload = { 'file_path': new_file_path, 'dest_path': new_folder_path, 'back_url': config['bfm_url'] } response = requests.post(url, json=payload, headers=headers) if response.status_code == 200: print("Request was successful.") print("Response: ", response.json()) else: print("Request failed.") # 特征识别 xml_path = new_folder_path + '/' + item.name.rpartition('.')[0] + '_FeatrueTable.XML' print('xml_path', xml_path) parser_obj = pc.FeatureParser(xml_path) print('parser_obj', parser_obj) slot = parser_obj.slots print('slot', slot) hole = parser_obj.holes print('hole', hole) size = parser_obj.size print('size', size) open_slot = parser_obj.open_slots print('open_slot', open_slot) vector = parser_obj.vectors print('vector', vector) print('all parcer', size) try: hole_time = pc.hole_time(parser_obj) print('hole_time', hole_time) except Exception as e: return json.dumps({'code': 400, 'msg': '孔尺寸超限', 'error_msg': str(e)}) try: slot_time = pc.slot_time(parser_obj) print('slot_time', slot_time) except Exception as e: return json.dumps({'code': 400, 'msg': '槽尺寸超限', 'error_msg': str(e)}) try: open_slot_time = pc.open_slot_time(parser_obj) print('open_slot_time', open_slot_time) except Exception as e: return json.dumps({'code': 400, 'msg': '开口槽尺寸超限', 'error_msg': str(e)}) total_time = hole_time + slot_time + open_slot_time print(hole_time, slot_time, open_slot_time) print('total_time', total_time) ret = {'feature_infos': [{'name': 'all_feature', 'type': '铣', 'process_time': total_time}], 'boxshape': size, 'slugX': 10.0, 'slugY': 90.0, 'slugZ': 42.0, 'turn_over_times': 2, 'target_faces': ['A', 'B']} self.model_feature = json.dumps(ret['feature_infos'], ensure_ascii=False) self.model_length = size['length'] # 长 单位mm self.model_width = size['width'] # 宽 self.model_height = size['height'] # 高 self.model_volume = size['length'] * size['width'] * size['height'] # 附件处理 base64_data = base64.b64encode(item.datas) base64_datas = base64_data.decode('utf-8') model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() # 读取文件 shapes = read_step_file(new_file_path) output_file = os.path.join(new_folder_path, str(model_code) + '.stl') write_stl_file(shapes, output_file, 'binary', 0.03, 0.5) # 转化为glb output_glb_file = os.path.join(new_folder_path, str(model_code) + '.glb') util_path = get_resource_path('jikimo_gateway_api', 'static/util') # 根据操作系统确定使用 'python' 还是 'python3' python_cmd = 'python3' if os_name == 'Linux' else 'python' print('python_cmd', python_cmd) print('os_name', os_name) # 使用引号包围路径 cmd = '%s "%s/stl2gltf.py" "%s" "%s" -b' % (python_cmd, util_path, output_file, output_glb_file) logging.info(cmd) os.system(cmd) # 转base64 with open(output_glb_file, 'rb') as fileObj: image_data = fileObj.read() base64_data = base64.b64encode(image_data) return base64_data except Exception as e: return UserError('模型自动报价失败,请联系管理员') # 将attach的datas内容转为glb文件 def transition_glb_file(self, report_path, model_code): shapes = read_step_file(report_path) output_file = os.path.join('/tmp', str(model_code) + '.stl') write_stl_file(shapes, output_file, 'binary', 0.03, 0.5) # 转化为glb output_glb_file = os.path.join('/tmp', str(model_code) + '.glb') util_path = get_resource_path('sf_base', 'static/util') cmd = 'python3 %s/stl2gltf.py %s %s -b' % (util_path, output_file, output_glb_file) os.system(cmd) # 转base64 with open(output_glb_file, 'rb') as fileObj: image_data = fileObj.read() base64_data = base64.b64encode(image_data) return base64_data # return False @api.onchange('upload_model_file') def onchange_model_file(self): for item in self: if len(item.upload_model_file) > 1: raise ValidationError('只允许上传一个文件') if item.upload_model_file: file_attachment_id = item.upload_model_file[0] item.model_file = self.model_analyze(file_attachment_id) self._get_price(item) else: item.model_file = False item.model_feature = False item.model_length = 0 item.model_width = 0 item.model_height = 0 item.model_volume = 0 def distribute_to_factory(self, obj): """ 派单到工厂 :return: """ try: logging.info('---------派单到工厂-------') res = {'bfm_process_order_list': []} for item in obj: attachment = item.upload_model_file[0] base64_data = base64.b64encode(attachment.datas) base64_datas = base64_data.decode('utf-8') barcode = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() # logging.info('model_file-size: %s' % len(item.model_file)) res['bfm_process_order_list'].append({ 'model_long': item.model_length, 'model_width': item.model_width, 'model_height': item.model_height, 'model_volume': item.model_volume, 'model_machining_precision': item.machining_precision, 'model_name': attachment.name, 'model_data': base64_datas, 'model_file': base64.b64encode(item.model_file).decode('utf-8'), 'texture_code': item.material_id.materials_no, 'texture_type_code': item.material_model_id.materials_no, # 'surface_process_code': self.env['jikimo.surface.process']._json_surface_process_code(item), 'process_parameters_code': self.env[ 'sf.production.process.parameter']._json_production_process_item_code( item), 'price': item.price, 'number': item.quantity, 'total_amount': item.price, 'remark': '', 'barcode': barcode }) # res['bfm_process_order_list'] = json.dumps(res['bfm_process_order_list']) product_id = self.env.ref('sf_dlm.product_template_sf').sudo() self_machining_id = self.env.ref('sf_dlm.product_embryo_sf_self_machining').sudo() outsource_id = self.env.ref('sf_dlm.product_embryo_sf_outsource').sudo() purchase_id = self.env.ref('sf_dlm.product_embryo_sf_purchase').sudo() company_id = self.env.ref('base.main_company').sudo() # user_id = request.env.ref('base.user_admin').sudo() order_id = self.env['sale.order'].sale_order_create(company_id, 'XXXXX', 'XXXXX', 'XXXXX', str(datetime.now()), '现结', '支付宝') i = 1 # 给sale_order的default_code字段赋值 aa = self.env['sale.order'].sudo().search([('name', '=', order_id.name)]) logging.info('---------aa------- %s' % aa.name) aa.default_code = obj.name for item in res['bfm_process_order_list']: product = self.env['product.template'].sudo().product_create(product_id, item, order_id, obj.name, i) bom_data = self.env['mrp.bom'].get_bom(product) logging.info('bom_data:%s' % bom_data) if bom_data: bom = self.env['mrp.bom'].bom_create(product, 'normal', False) bom.bom_create_line_has(bom_data) else: if product.materials_type_id.gain_way == '自加工': # 创建坯料 self_machining_embryo = self.env['product.template'].sudo().no_bom_product_create( self_machining_id, item, order_id, 'self_machining', i) # 创建坯料的bom self_machining_bom = self.env['mrp.bom'].bom_create(self_machining_embryo, 'normal', False) # 创建坯料里bom的组件 self_machining_bom_line = self_machining_bom.bom_create_line(self_machining_embryo) if self_machining_bom_line is False: self.cr.rollback() return UserError('该订单模型的材料型号在您分配的工厂里暂未有原材料,请先配置再进行分配') # 产品配置bom product_bom_self_machining = self.env['mrp.bom'].bom_create(product, 'normal', False) product_bom_self_machining.bom_create_line_has(self_machining_embryo) elif product.materials_type_id.gain_way == '外协': # 创建坯料 outsource_embryo = self.env['product.template'].sudo().no_bom_product_create(outsource_id, item, order_id, 'subcontract', i) # 创建坯料的bom outsource_bom = self.env['mrp.bom'].bom_create(outsource_embryo, 'subcontract', True) # 创建坯料的bom的组件 outsource_bom_line = outsource_bom.with_user( self.env.ref("base.user_admin")).bom_create_line(outsource_embryo) if outsource_bom_line is False: self.cr.rollback() return UserError('该订单模型的材料型号在您分配的工厂里暂未有原材料,请先配置再进行分配') # 产品配置bom product_bom_outsource = self.env['mrp.bom'].bom_create(product, 'normal', False) product_bom_outsource.bom_create_line_has(outsource_embryo) elif product.materials_type_id.gain_way == '采购': purchase_embryo = self.env['product.template'].sudo().no_bom_product_create(purchase_id, item, order_id, 'purchase', i) # 产品配置bom product_bom_purchase = self.env['mrp.bom'].bom_create(product, 'normal', False) product_bom_purchase.bom_create_line_has(purchase_embryo) order_id.with_user(self.env.ref("base.user_admin")).sale_order_create_line(product, item) except Exception as e: # self.cr.rollback() return UserError('工厂创建销售订单和产品失败,请联系管理员') # 特征识别 def feature_recognition(self, report_path, model_code): feature_path = self.env['sf.auto_quatotion.common'].sudo().get_feature_full_path() process_time_db_path = self.env['sf.auto_quatotion.common'].sudo().get_process_time_db_path() ret = self.env['sf.auto_quatotion.common'].sudo().get_auto_quatotion(report_path, feature_path, process_time_db_path, model_code) return ret # 模型上色 def model_coloring(self, order): url = '/api/library_of_models/create' config = self.env['res.config.settings'].get_values() config_header = Common.get_headers(self, config['token'], config['sf_secret_key']) logging.info('order: %s' % order.name) if order: attachment = order.upload_model_file[0] base64_data = base64.b64encode(attachment.datas) base64_datas = base64_data.decode('utf-8') model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() logging.info('model_file-size: %s' % len(order.model_file)) logging.info('attachment.datas-size: %s' % len(attachment.datas)) vals = { 'model_code': model_code, 'model_data': base64_data, 'model_name': attachment.name, 'model_long': order.model_length, 'model_width': order.model_width, 'model_height': order.model_height, 'model_volume': order.model_volume, 'model_order_no': order.name, 'remark': '订单号:%s 客户:%s' % (order.name, order.customer_id.name) } try: ret = requests.post((config['sf_url'] + url), json={}, data=vals, headers=config_header, timeout=60) ret = ret.json() # result = json.loads(ret['result']) if ret['status'] == 1: order.model_color_state = 'success' else: order.model_color_state = 'fail' raise UserError(ret['message']) except Exception as e: order.model_color_state = 'fail' raise UserError("模型上色失败") # 自动报价 def _get_price(self, order): url = '/api/automatic_quotes' config = self.env['res.config.settings'].sudo().get_values() config_header = Common.get_headers(self, config['token'], config['sf_secret_key']) logging.info("报价接口..........% s" % order.name) try: if order: vals = {} # mrs合作伙伴token vals['token'] = config['token'] vals['accuracy'] = order.machining_precision vals['number'] = order.quantity vals['process_code'] = 0 vals['texture_code'] = order.material_model_id.materials_no vals['delivery_days'] = 15 if order.model_file: for item in order.upload_model_file: if item.ids[0]: logging.info('create-attachment:%s' % int(item.ids[0])) attachment = self.env['ir.attachment'].sudo().search([('id', '=', int(item.ids[0]))]) vals['attachment_id'] = attachment.id else: vals['attachment_id'] = '' vals['feature_infos'] = order.model_feature vals['model_long'] = order.model_length vals['model_width'] = order.model_width vals['model_height'] = order.model_height logging.info('vals:%s' % vals) ret = requests.post((config['sf_url'] + url), json={}, data=vals, headers=config_header) result = json.dumps(json.loads(ret.text), ensure_ascii=False, indent=4, separators=(',', ':')) logging.info('报价接口返回:%s' % result) price_result = json.loads(result) # random.randint(0, 10000) order.write({'price': price_result.get('price')}) else: raise UserError("订单不存在") except Exception as e: if ret['status'] != 1: raise UserError(e) else: raise UserError("自动报价失败,请联系管理员")