diff --git a/sf_machine_connect/views/res_config_settings_views.xml b/sf_machine_connect/views/res_config_settings_views.xml index bdd481ac..01067754 100644 --- a/sf_machine_connect/views/res_config_settings_views.xml +++ b/sf_machine_connect/views/res_config_settings_views.xml @@ -8,14 +8,14 @@
-

bfm环境配置

+

业务平台参数配置

-
diff --git a/sf_mrs_connect/models/res_config_setting.py b/sf_mrs_connect/models/res_config_setting.py index 3f2b698f..b15c71df 100644 --- a/sf_mrs_connect/models/res_config_setting.py +++ b/sf_mrs_connect/models/res_config_setting.py @@ -13,8 +13,8 @@ class ResConfigSettings(models.TransientModel): token = fields.Char(string='TOKEN', default='b811ac06-3f00-11ed-9aed-0242ac110003') sf_secret_key = fields.Char(string='密钥', default='wBmxej38OkErKhD6') sf_url = fields.Char(string='访问地址', default='https://sf.cs.jikimo.com') - bfm_url = fields.Char(string='业务平台后端访问地址', default='https://bfm.jikimo.com') agv_url = fields.Char(string='avg访问地址', default='http://IP:PORT/rcms/services/rest') + model_parser_url = fields.Char('特征识别路径') ftp_host = fields.Char(string='FTP的ip') ftp_port = fields.Char(string='FTP端口') ftp_user = fields.Char(string='FTP用户') diff --git a/sf_mrs_connect/views/res_config_settings_views.xml b/sf_mrs_connect/views/res_config_settings_views.xml index bc829b2f..08812f23 100644 --- a/sf_mrs_connect/views/res_config_settings_views.xml +++ b/sf_mrs_connect/views/res_config_settings_views.xml @@ -8,8 +8,8 @@
-

同步参数配置

-
+

云平台参数配置

+
@@ -36,7 +36,7 @@

FTP参数配置

-
+
@@ -61,14 +61,14 @@
-

业务平台参数配置

-
+

特征识别参数配置

+
-
diff --git a/sf_sale/models/__init__.py b/sf_sale/models/__init__.py index d8b09a48..99b143c7 100644 --- a/sf_sale/models/__init__.py +++ b/sf_sale/models/__init__.py @@ -1,3 +1,6 @@ from . import sale_order from . import quick_easy_order from . import auto_quatotion_common +from . import parser_and_calculate_work_time +from . import preload_datas_functions + diff --git a/sf_sale/models/feature.sqlite b/sf_sale/models/feature.sqlite deleted file mode 100644 index e69de29b..00000000 diff --git a/sf_sale/models/parser_and_calculate_work_time.py b/sf_sale/models/parser_and_calculate_work_time.py new file mode 100644 index 00000000..f5abcb7f --- /dev/null +++ b/sf_sale/models/parser_and_calculate_work_time.py @@ -0,0 +1,472 @@ +import time +# import pandas as pd +from lxml import etree +from collections import Counter +from . import preload_datas_functions as preload + + +# import preload_datas_functions as preload + + +class FeatureParser: + """ + 解析Feature.xml文件 + """ + + def __init__(self, xml_file): + self.root = etree.parse(xml_file).getroot() + self.size = self._get_size() + self.holes = self._get_holes() + self.slots = self._get_slot() + self.open_slots = self._get_open_slot() + self.vectors = self._get_vectors() + + def _get_size(self): + size = self.root.find('Size') + return { + 'length': float(size.get('Length')), + 'width': float(size.get('Width')), + 'height': float(size.get('Height')) + } + + def _get_vectors(self): + vectors = {} + for item in self.root.findall('.//Item'): + vector = item.find('Vector') + if vector is not None: + key = (vector.get('i'), vector.get('j'), vector.get('k')) + vectors[key] = vectors.get(key, 0) + 1 + return vectors + + def get_vector_counts(self): + return len(self.vectors) + + def _get_holes(self): + holes = [] + hole_element = self.root.find('Hole') + if hole_element is not None: + for item in self.root.find('Hole').iter('Item'): + hole = {} # 每个hole是一个字典 + hole['id'] = int(item.get('ID')) + hole['name'] = item.get('Name') + hole['red'] = int(item.get('Red')) + hole['green'] = int(item.get('Green')) + hole['blue'] = int(item.get('Blue')) + # 处理circles + circles = [] + for circle in item.iter('Circle'): + circles.append({ + 'x': float(circle.get('x')), + 'y': float(circle.get('y')), + 'z': float(circle.get('z')), + 'rad': float(circle.get('rad')) + }) + hole['circles'] = circles + + # 处理bottom + bottoms = [] + for bottom in item.iter('Bottom'): + bottoms.append({ + 'x': float(bottom.get('x')), + 'y': float(bottom.get('y')), + 'z': float(bottom.get('z')), + 'rad': float(bottom.get('rad')) + }) + hole['bottoms'] = bottoms + + # 处理vector + for vector in item.iter('Vector'): + hole['vector'] = { + 'i': float(vector.get('i')), + 'j': float(vector.get('j')), + 'k': float(vector.get('k')) + } + + # 创建元组并添加到列表中 + z_rad_tuples = [] + max_z = None + non_zero_rads = set() # 使用set来存储rad值,自动去重 + for circle in circles: + z = float(circle.get('z')) + rad = float(circle.get('rad')) + if max_z is None or z > max_z: + max_z = z + if rad != 0: + non_zero_rads.add(rad) + for rad in non_zero_rads: + z_rad_tuple = (max_z, rad) + z_rad_tuples.append(z_rad_tuple) + hole['z_rad_tuples'] = z_rad_tuples + + holes.append(hole) # 添加到holes列表中 + + return holes + + def _get_slot(self): + """ + 获取slot信息 + """ + slots = [] + slot_a_list = [] + slot_element = self.root.find('Slot') + if slot_element is not None: + for item in self.root.find('Slot').iter('Item'): + slot = {} + slot['id'] = int(item.get('ID')) + slot['name'] = item.get('Name') + slot['red'] = int(item.get('Red')) + slot['green'] = int(item.get('Green')) + slot['blue'] = int(item.get('Blue')) + # 获取Volume和Area信息 + volume = item.find('Volume') + if volume is not None: + slot['volume'] = float(volume.get('value')) + + area = item.find('Area') + if area is not None: + slot['area'] = float(area.get('value')) + slot_a_list.append(slot['area']) + # 处理lines + lines = [] + for line in item.iter('Line'): + lines.append({ + 'type': line.get('Type'), # 'type' : 'line' or 'arc + 'x1': float(line.get('x1')), + 'y1': float(line.get('y1')), + 'z1': float(line.get('z1')), + 'x2': float(line.get('x2')), + 'y2': float(line.get('y2')), + 'z2': float(line.get('z2')) + }) + slot['lines'] = lines + + # 处理Arc + arcs = [] + for arc in item.iter('Arc'): + arcs.append({ + 'type': arc.get('Type'), + 'x1': float(arc.get('x1')), + 'y1': float(arc.get('y1')), + 'z1': float(arc.get('z1')), + 'x2': float(arc.get('x2')), + 'y2': float(arc.get('y2')), + 'z2': float(arc.get('z2')), + 'x3': float(arc.get('x3')), + 'y3': float(arc.get('y3')), + 'z3': float(arc.get('z3')) + + }) + slot['arcs'] = arcs + slot['a'] = slot_a_list + slots.append(slot) + return slots + + def _get_open_slot(self): + """ + 获取open_slot信息 + """ + open_slots = [] + open_slot_v_list = [] + open_slot_element = self.root.find('OpenSlot') + if open_slot_element is not None: + for item in self.root.find('OpenSlot').iter('Item'): + open_slot = {} + open_slot['id'] = int(item.get('ID')) + open_slot['name'] = item.get('Name') + open_slot['red'] = int(item.get('Red')) + open_slot['green'] = int(item.get('Green')) + open_slot['blue'] = int(item.get('Blue')) + # 获取Volume和Area信息 + volume = item.find('Volume') + if volume is not None: + open_slot['volume'] = float(volume.get('value')) + + area = item.find('Area') + if area is not None: + open_slot['area'] = float(area.get('value')) + # open_slot_v_list.append(round(open_slot['volume'] / open_slot['area'], 3)) + open_slot_v_list.append(open_slot['area']) + # 处理lines + lines = [] + for line in item.iter('Line'): + lines.append({ + 'type': line.get('Type'), # 'type' : 'line' or 'arc + 'x1': float(line.get('x1')), + 'y1': float(line.get('y1')), + 'z1': float(line.get('z1')), + 'x2': float(line.get('x2')), + 'y2': float(line.get('y2')), + 'z2': float(line.get('z2')) + }) + open_slot['lines'] = lines + + # 处理Arc + arcs = [] + for arc in item.iter('Arc'): + arcs.append({ + 'type': arc.get('Type'), + 'x1': float(arc.get('x1')), + 'y1': float(arc.get('y1')), + 'z1': float(arc.get('z1')), + 'x2': float(arc.get('x2')), + 'y2': float(arc.get('y2')), + 'z2': float(arc.get('z2')), + 'x3': float(arc.get('x3')), + 'y3': float(arc.get('y3')), + 'z3': float(arc.get('z3')) + + }) + open_slot['arcs'] = arcs + open_slot['v'] = open_slot_v_list + open_slots.append(open_slot) + return open_slots + + +def hole_time(parser): + """ + 计算孔的工时 + :return: + """ + # 判断是否有孔 + if parser.holes is not None: + # 遍历所有的孔,获取孔径和孔深度,然后调用函数查询工时 + hole_total_time = 0 + nums = 1 + expand_hole = '否' + j_time = 0 + j_hole_nums = 0 + hole_nums = 0 + for hole in parser.holes: + for z_rad_tuple in hole['z_rad_tuples']: + if (2 * z_rad_tuple[1] * z_rad_tuple[0] <= 3750) and (2 * z_rad_tuple[1] <= 25): + hole_nums += 1 + # print('z_rad_tuple', z_rad_tuple) + per_time_minute = preload.get_suitable_hole_working_hours(preload.df_hole_duration, + 2 * z_rad_tuple[1], + z_rad_tuple[0]) + # if per_time_minute is None: + # raise Exception('孔径为%s,深度为%s的孔没有找到对应的工时' % (2 * z_rad_tuple[1], z_rad_tuple[0])) + # print('per_time_minute', per_time_minute) + expand_hole_end = 0.6 if expand_hole == '是' else 1 + per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60 + hole_total_time += per_time + elif (2 * z_rad_tuple[1] * z_rad_tuple[0] <= 3750) and (2 * z_rad_tuple[1] > 25): + hole_nums += 1 + # print('z_rad_tuple', z_rad_tuple) + per_time_minute = 0.0003 * 2 * z_rad_tuple[1] * z_rad_tuple[0] + expand_hole_end = 0.6 if expand_hole == '是' else 1 + per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60 + hole_total_time += per_time + elif 3750 < 2 * z_rad_tuple[1] * z_rad_tuple[0] <= 50000: + hole_nums += 1 + # print('z_rad_tuple', z_rad_tuple) + per_time_minute = 0.0003 * 2 * z_rad_tuple[1] * z_rad_tuple[0] + expand_hole_end = 0.6 if expand_hole == '是' else 1 + per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60 + hole_total_time += per_time + elif 50000 < 2 * z_rad_tuple[1] * z_rad_tuple[0] <= 100000: + hole_nums += 1 + # print('z_rad_tuple', z_rad_tuple) + per_time_minute = 0.00018 * 2 * z_rad_tuple[1] * z_rad_tuple[0] + expand_hole_end = 0.6 if expand_hole == '是' else 1 + per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60 + hole_total_time += per_time + elif 100000 < 2 * z_rad_tuple[1] * z_rad_tuple[0] <= 150000: + hole_nums += 1 + # print('z_rad_tuple', z_rad_tuple) + per_time_minute = 0.00016 * 2 * z_rad_tuple[1] * z_rad_tuple[0] + expand_hole_end = 0.6 if expand_hole == '是' else 1 + per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60 + hole_total_time += per_time + elif 150000 < 2 * z_rad_tuple[1] * z_rad_tuple[0] <= 200000: + hole_nums += 1 + # print('z_rad_tuple', z_rad_tuple) + per_time_minute = 0.00015 * 2 * z_rad_tuple[1] * z_rad_tuple[0] + expand_hole_end = 0.6 if expand_hole == '是' else 1 + per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60 + hole_total_time += per_time + elif 200000 < 2 * z_rad_tuple[1] * z_rad_tuple[0] <= 250000: + hole_nums += 1 + # print('z_rad_tuple', z_rad_tuple) + per_time_minute = 0.0002 * 2 * z_rad_tuple[1] * z_rad_tuple[0] + expand_hole_end = 0.6 if expand_hole == '是' else 1 + per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60 + hole_total_time += per_time + else: + raise Exception('孔径为%s,深度为%s的孔没有找到对应的工时' % (2 * z_rad_tuple[1], z_rad_tuple[0])) + + print('孔工时', round(hole_total_time * nums * 2) / 2) + print('共有%s个孔,其中%s为台阶孔' % (len(parser.holes), hole_nums - len(parser.holes))) + return round(hole_total_time * nums * 2) / 2 + else: + return 0 + + +def slot_time(parser): + # 判断是否有槽 + if parser.slots is not None: + # 遍历所有的槽,获取槽的长度,然后调用函数查询工时 + slot_total_time = 0 + nums = 1 + finish_time = 0 + process_time = 0 + process_total_time = 0 + slot_a = parser.slots[0]['a'] + + slot_a_counter = Counter(slot_a) + slot_a_counter_result = dict(slot_a_counter) + + for i in slot_a_counter_result: + for slot in parser.slots: + if slot['area'] == i: + # # 计算长度(第一条线和第三条线的X轴距离) + # length = abs(slot['lines'][0]['y1'] - slot['lines'][2]['y1']) + # # 计算宽度(第一条线和第二条线的Y轴距离) + # width = abs(slot['lines'][1]['x1'] - slot['lines'][3]['x1']) + # # 计算面积 + # area = length * width + # 槽深度 + depth = round(slot['volume'] / slot['area'], 3) + + # 沟通刀具暂定为12 + finish_tool_diameter = 12 + if 200 < slot['area'] <= 5000: + finish_time = 0 + + # 加工穴数待定(取得每一个槽的穴数和装夹次数,那这个数量?目前暂时按1来算,待有统计数据之后再说) + rough_part_nums = slot_a_counter_result[slot['area']] + rough_clamping_times = 1 + finishi_part_nums = 1 + finish_clamping_times = 1 + # 调用函数计算槽的工时 + slot_total_time = preload.get_suitable_rough_working_hours(preload.df_rough_duration, depth) + # if nums > 20: + # process_time = round( + # (nums * 0.6 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + ( + # finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + # elif nums > 10: + # process_time = round( + # (nums * 0.7 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + ( + # finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + # elif nums > 6: + # process_time = round( + # (nums * 0.8 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + ( + # finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + # elif nums > 4: + # process_time = round( + # (nums * 0.9 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + ( + # finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + # elif nums > 1: + # process_time = round( + # (nums * 0.95 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + ( + # finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + # else: + # process_time = round( + # (nums * 1 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + ( + # finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + process_time = round( + (nums * 1 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + ( + finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + # print('slot_total_time', slot_total_time) + elif slot['area'] <= 200: + slot_total_time = 0 + rough_part_nums = slot_a_counter_result[slot['area']] + rough_clamping_times = 1 + finishi_part_nums = 1 + finish_clamping_times = 1 + # 调用函数计算槽的工时 + finish_time = preload.get_suitable_finish_working_hours(preload.df_finish_duration, depth, + finish_tool_diameter) + process_time = round( + (nums * 1 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + ( + finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + # print('finish_time', finish_time) + else: + rough_part_nums = slot_a_counter_result[slot['area']] + rough_clamping_times = 1 + finishi_part_nums = 1 + finish_clamping_times = 1 + process_time = round( + (nums * 1 * ((0.00016 * rough_part_nums + rough_clamping_times * 20) + ( + finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + process_total_time += process_time + + print('槽工时', process_total_time) + return process_total_time + else: + return 0 + + +def open_slot_time(parser): + # 判断是否有开口槽 + if parser.open_slots is not None: + # 遍历所有的开口槽,获取槽宽和槽长,然后调用函数查询工时 + open_slot_total_time = 0 + nums = 1 + finish_time = 0 + open_slot_process_time = 0 + + open_slot_v = parser.open_slots[0]['v'] + + counter = Counter(open_slot_v) + result = dict(counter) + + transiant_time = 0 + for i in result: + for open_slot in parser.open_slots: + if open_slot['area'] == i: + depth = round(open_slot['volume'] / open_slot['area'], 3) + # 沟通刀具暂定为12 + finish_tool_diameter = 12 + if 200 < open_slot['area'] <= 5000: + finish_time = 0 + # 加工穴数待定(取得每一个槽的穴数和装夹次数,那这个数量?目前暂时按1来算,待有统计数据之后再说) + rough_part_nums = result[open_slot['area']] + rough_clamping_times = 1 + finishi_part_nums = 1 + finish_clamping_times = 1 + # 调用函数计算槽的工时 + slot_total_time = preload.get_suitable_rough_working_hours(preload.df_rough_duration, depth) + open_slot_process_time = round( + (nums * 1 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + ( + finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + # print('slot_total_time', slot_total_time) + elif open_slot['area'] <= 200: + slot_total_time = 0 + rough_part_nums = 1 + rough_clamping_times = 1 + finishi_part_nums = 1 + finish_clamping_times = 1 + # 调用函数计算槽的工时 + finish_time = preload.get_suitable_finish_working_hours(preload.df_finish_duration, depth, + finish_tool_diameter) + open_slot_process_time = round( + (nums * 1 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + ( + finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + # print('finish_time', finish_time) + else: + rough_part_nums = 1 + rough_clamping_times = 1 + finishi_part_nums = 1 + finish_clamping_times = 1 + open_slot_process_time = round( + (nums * 1 * ((0.00016 * rough_part_nums + rough_clamping_times * 20) + ( + finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2 + + transiant_time += open_slot_process_time + print('开口槽工时', transiant_time) + return transiant_time + else: + return 0 + + +if __name__ == '__main__': + time1 = time.time() + parser = FeatureParser( + 'D:\\ccccccccccccccccccccccccccccccccccccccccccccccc\\aa\\JKM001-260.200.30_FeatureTable.xml') + # print('parser', parser.holes) + # print('parser.slots', parser.slots) + # print('parser.open_slots', parser.open_slots) + print('总工时', hole_time(parser) + slot_time(parser) + open_slot_time(parser)) + time2 = time.time() + print('耗时:', time2 - time1) diff --git a/sf_sale/models/preload_datas_functions.py b/sf_sale/models/preload_datas_functions.py new file mode 100644 index 00000000..37035f44 --- /dev/null +++ b/sf_sale/models/preload_datas_functions.py @@ -0,0 +1,201 @@ +import psycopg2 +# import pandas as pd + + +def load_and_convert_data(table_name, column_names): + connection = None + data = [] + + try: + # connection = psycopg2.connect(user="odoo", + # password="odoo", + # host="localhost", + # port="5432", + # database="www1") + connection = psycopg2.connect(user="odoo", + password="odoo", + host="120.76.195.146", + port="15432", + database="bfm_dev1") + + cursor = connection.cursor() + + # Construct the query string using the table name passed in + query = f"SELECT {', '.join(column_names)} FROM {table_name};" + cursor.execute(query) + + # Fetch all rows from cursor + data = cursor.fetchall() + + except (Exception, psycopg2.Error) as error: + print("Error fetching data from PostgreSQL table", error) + + finally: + # Always close database connection after work done + if (connection): + cursor.close() + connection.close() + + # Convert the list of tuples to DataFrame + # df = pd.DataFrame(data, columns=column_names) + # + # # Convert all string columns to float + # for col in df.columns: + # if df[col].dtype == 'object': + # df[col] = df[col].astype(float) + + return 'df' + + +def get_suitable_hole_working_hours(df, target_diameter, target_depth): + """ + 从钻孔、铰孔数据中获取符合要求的最小工时 + """ + # df为输入的数据,target_diameter为目标孔径,target_depth为目标孔深 + + df_diameter_filtered = df.loc[df['hole_diameter'] >= target_diameter] + + if not df_diameter_filtered.empty: + min_diameter = df_diameter_filtered['hole_diameter'].min() + df_depth_filtered = df_diameter_filtered.loc[ + (df_diameter_filtered['hole_diameter'] == min_diameter) & ( + df_diameter_filtered['hole_depth'] >= target_depth)] + + if not df_depth_filtered.empty: + min_depth_row = df_depth_filtered.loc[df_depth_filtered['hole_depth'].idxmin()] + min_working_hours = min_depth_row['working_hours'] + return min_working_hours + else: + print("No records found where hole_depth is bigger than the target depth") + return None + else: + print("No records found where hole_diameter is bigger than the target diameter") + return None + + +def get_suitable_blank_working_hours(df, blank_height, blank_length, blank_width): + """ + 从毛坯数据中获取符合要求的最小工时 + """ + # df为输入的数据,blank_height为目标毛坯高度,blank_length为目标毛坯长度,blank_width为目标毛坯宽度 + + df_height_filtered = df.loc[df['blank_height'] >= blank_height] + + if not df_height_filtered.empty: + min_height = df_height_filtered['blank_height'].min() + df_length_filtered = df_height_filtered.loc[ + (df_height_filtered['blank_height'] == min_height) & ( + df_height_filtered['blank_length'] >= blank_length)] + + if not df_length_filtered.empty: + min_length_row = df_length_filtered.loc[df_length_filtered['blank_length'].idxmin()] + min_working_hours = min_length_row['working_hours'] + return min_working_hours + else: + print("No records found where blank_length is bigger than the target length") + return None + else: + print("No records found where blank_height is bigger than the target height") + return None + + +def get_suitable_rough_working_hours(df, rough_depth): + """ + 从粗加工数据中获取符合要求的最小工时 + """ + # df为输入的数据,rough_depth为目标粗加工深度 + + df_depth_filtered = df.loc[df['rough_depth'] >= rough_depth] + + if not df_depth_filtered.empty: + min_depth_row = df_depth_filtered.loc[df_depth_filtered['rough_depth'].idxmin()] + min_working_hours = min_depth_row['working_hours'] + return min_working_hours + else: + print("No records found where rough_depth is bigger than the target depth") + return None + + +def get_suitable_finish_working_hours(df, finish_depth, finish_tool_diameter): + """ + 从精加工数据中获取符合要求的最小工时 + """ + # df为输入的数据,finish_depth为目标精加工深度,finish_tool_diameter为目标精加工刀具直径 + + df_depth_filtered = df.loc[df['finish_depth'] >= finish_depth] + + if not df_depth_filtered.empty: + min_depth = df_depth_filtered['finish_depth'].min() + df_tool_diameter_filtered = df_depth_filtered.loc[ + (df_depth_filtered['finish_depth'] == min_depth) & ( + df_depth_filtered['finish_tool_diameter'] >= finish_tool_diameter)] + + if not df_tool_diameter_filtered.empty: + min_tool_diameter_row = df_tool_diameter_filtered.loc[ + df_tool_diameter_filtered['finish_tool_diameter'].idxmin()] + min_working_hours = min_tool_diameter_row['working_hours'] + return min_working_hours + else: + print("No records found where finish_tool_diameter is bigger than the target tool diameter") + return None + else: + print("No records found where finish_depth is bigger than the target depth") + return None + + +def get_suitable_chamfer_working_hours(df, chamfer_length, chamfer_size): + """ + 根据倒角长度获得倒角工时,装夹平面耗时clamping_type_plane和装夹斜面耗时clamping_type_slope + """ + # df为输入的数据,chamfer_length为目标倒角长度,clamping_type_plane为目标装夹平面耗时,clamping_type_slope为目标装夹斜面耗时 + + df_length_filtered = df.loc[df['chamfer_length'] >= chamfer_length] + df_chamfer_size_filtered = df.loc[df['chamfer_size'] >= chamfer_size] + + if not df_length_filtered.empty and not df_chamfer_size_filtered.empty: + min_length_row = df_length_filtered.loc[df_length_filtered['chamfer_length'].idxmin()] + min_chamfer_size_row = df_chamfer_size_filtered.loc[df_chamfer_size_filtered['chamfer_size'].idxmin()] + clamping_time = min_length_row['clamping_time'] + clamping_type_plane = min_length_row['clamping_type_plane'] + clamping_type_slope = min_length_row['clamping_type_slope'] + coefficient = min_chamfer_size_row['coefficient'] + return clamping_time, clamping_type_plane, clamping_type_slope, coefficient + else: + print("No records found where chamfer_length is bigger than the target length") + return None + + +df_hole_duration = load_and_convert_data('hole_duration', ['hole_diameter', 'hole_depth', 'working_hours']) + +df_j_hole_duration = load_and_convert_data('j_hole_duration', ['hole_diameter', 'hole_depth', 'working_hours']) + +df_chamfer_duration = load_and_convert_data('chamfer_duration', + ['chamfer_length', 'clamping_time', 'chamfer_size', 'coefficient', + 'clamping_type_plane', 'clamping_type_slope']) + +df_blank_duration = load_and_convert_data('blank_duration', + ['blank_length', 'blank_width', 'blank_height', 'working_hours']) + +df_rough_duration = load_and_convert_data('rough_duration', ['rough_depth', 'working_hours']) + +df_finish_duration = load_and_convert_data('finish_duration', + ['finish_depth', 'finish_tool_diameter', 'working_hours']) + + +if __name__ == '__main__': + min_working_hours = get_suitable_hole_working_hours(df_hole_duration, 24, 150) + print('min_working_hours', min_working_hours) + min_j_working_hours = get_suitable_hole_working_hours(df_j_hole_duration, 10, 15) + print('min_j_working_hours', min_j_working_hours) + min_blank_working_hours = get_suitable_blank_working_hours(df_blank_duration, 150, 300, 300) + print('min_blank_working_hours', min_blank_working_hours) + min_rough_working_hours = get_suitable_rough_working_hours(df_rough_duration, 49) + print('min_rough_working_hours', min_rough_working_hours) + min_finish_working_hours = get_suitable_finish_working_hours(df_finish_duration, 0.5, 10) + print('min_finish_working_hours', min_finish_working_hours) + clamping_time, clamping_type_plane, clamping_type_slope, coefficient = get_suitable_chamfer_working_hours( + df_chamfer_duration, 10, 1.5) + print('clamping_time', clamping_time) + print('clamping_type_plane', clamping_type_plane) + print('clamping_type_slope', clamping_type_slope) + print('coefficient', coefficient) diff --git a/sf_sale/models/price.sqlite b/sf_sale/models/price.sqlite deleted file mode 100644 index e69de29b..00000000 diff --git a/sf_sale/models/process_time.db b/sf_sale/models/process_time.db deleted file mode 100644 index e69de29b..00000000 diff --git a/sf_sale/models/quick_easy_order.py b/sf_sale/models/quick_easy_order.py index 1e5f274c..7e6b8c78 100644 --- a/sf_sale/models/quick_easy_order.py +++ b/sf_sale/models/quick_easy_order.py @@ -2,17 +2,19 @@ import logging import base64 import hashlib import os +import platform import json from datetime import datetime import requests -from OCC.Extend.DataExchange import read_step_file -from OCC.Extend.DataExchange import write_stl_file +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 @@ -77,11 +79,11 @@ class QuickEasyOrder(models.Model): 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.transition_glb_file(report_path, model_code) + # 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) @@ -91,6 +93,147 @@ class QuickEasyOrder(models.Model): 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) @@ -116,24 +259,7 @@ class QuickEasyOrder(models.Model): raise ValidationError('只允许上传一个文件') if item.upload_model_file: file_attachment_id = item.upload_model_file[0] - # 附件路径 - report_path = file_attachment_id._full_path(file_attachment_id.store_fname) - logging.info("模型路径: %s" % report_path) - base64_data = base64.b64encode(file_attachment_id.datas) - base64_datas = base64_data.decode('utf-8') - model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() - logging.info("模型编码: %s" % model_code) - item.model_file = self.transition_glb_file(report_path, model_code) - ret = self.feature_recognition(report_path, model_code) - logging.info("自动报价返回值: %s" % ret) - boxshape = ret['boxshape'].tolist() - logging.info("自动报价boxshape: %s" % boxshape) - logging.info('自动报价feature_infos:%s' % ret['feature_infos']) - item.model_length = boxshape[0] # 长 单位mm - item.model_width = boxshape[1] # 宽 - item.model_height = boxshape[2] # 高 - item.model_volume = boxshape[0] * boxshape[1] * boxshape[2] - item.model_feature = json.dumps(ret['feature_infos'], ensure_ascii=False) + item.model_file = self.model_analyze(file_attachment_id) self._get_price(item) else: item.model_file = False diff --git a/sf_sale/security/ir.model.access.csv b/sf_sale/security/ir.model.access.csv index 6dc34877..566a7c70 100644 --- a/sf_sale/security/ir.model.access.csv +++ b/sf_sale/security/ir.model.access.csv @@ -1,5 +1,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_quick_easy_order,quick_easy_order,model_quick_easy_order,base.group_system,1,1,1,1 +access_quick_easy_order,quick_easy_order,model_quick_easy_order,base.group_system,1,1,1,0 +access_quick_easy_order_group_sale_salemanager,quick_easy_order_group_sale_salemanager,model_quick_easy_order,sf_base.group_sale_salemanager,1,1,1,0 +access_quick_easy_order_group_sale_director,quick_easy_order_group_sale_director,model_quick_easy_order,sf_base.group_sale_director,1,1,1,0 access_sf_auto_quatotion_common,sf_auto_quatotion_common,model_sf_auto_quatotion_common,base.group_system,1,1,1,1 access_sale_order_manager,sale_order_manager,model_sale_order,sf_base.group_sale_salemanager,1,1,1,0 access_sale_order_director,sale_order_director,model_sale_order,sf_base.group_sale_director,1,1,1,0