소스 검색

Merge branch 'cloud_configs' into cloud_configs_dir_fix

rambo 1 개월 전
부모
커밋
ddc4883c83

+ 32 - 14
python/api.py

@@ -32,9 +32,9 @@ from service.online_request.module_online_data import AIGCDataRequest
 import asyncio
 from fastapi import BackgroundTasks
 import functools
-import traceback
+import traceback,stat
 import concurrent.futures
-
+from sockets.message_handler import handlerFolderDelete
 def log_exception_with_context(context_message=""):
     """装饰器:为函数添加异常日志上下文"""
 
@@ -376,7 +376,7 @@ async def _process_non_excel_mode(params, goods_art_no_arrays):
         session = SqlQuery()
         pr = CRUD(PhotoRecord)
         images = pr.read_all(session, conditions={"goods_art_no": goods_art_no})
-        
+        session.close()
         if not images:
             raise UnicornException(
                 f"商品货号【{goods_art_no}】未查询到拍摄记录,请检查货号是否正确"
@@ -398,14 +398,14 @@ async def _process_excel_mode(goods_art_no_arrays,excel_df):
     limit_path = "{}/{}".format(settings.OUTPUT_DIR,time.strftime("%Y-%m-%d", time.localtime(time.time())))
     check_path(limit_path)
     
-    move_folder_array = check_move_goods_art_no_folder(settings.OUTPUT_DIR, goods_art_no_arrays, limit_path)
+    move_folder_array = check_move_goods_art_no_folder("output", goods_art_no_arrays, limit_path)
+    session = SqlQuery()
     for index, row in excel_df.iterrows():
             goods_art_no_image_dir = str(row["文件夹名称"])
             goods_art_no = str(row["商品货号"])
             print("货号数据", goods_art_no)
             if not goods_art_no:
                 raise UnicornException("货号不能为空")
-            session = SqlQuery()
             pr = CRUD(PhotoRecord)
             images = pr.read_all(session, conditions={"goods_art_no": goods_art_no})
             if not images:
@@ -415,6 +415,7 @@ async def _process_excel_mode(goods_art_no_arrays,excel_df):
             # 货号目录不存在再去进行移动和创建操作
             if move_folder_array.get(goods_art_no) is None:
                 await _process_image_copy_and_move(goods_art_no_image_dir, images,True)
+    session.close()
     return move_folder_array
 
 async def _process_image_copy_and_move(goods_art_no, images,is_excel):
@@ -440,7 +441,10 @@ async def _process_image_copy_and_move(goods_art_no, images,is_excel):
     if not resFlag:
         raise UnicornException(path)
     if is_excel:
-        shutil.rmtree(image_dir)
+        try:
+            shutil.rmtree(image_dir,onerror=settings.handle_remove_readonly)
+        except Exception as e:
+            logger.info(f"删除图片失败:{str(e)}")
 
 async def _build_config_data(params, goods_art_no_arrays):
     """构建配置数据"""
@@ -927,7 +931,6 @@ def get_device_tabs(type: int):
         .order_by(asc("id"))
     )
     result = session.exec(statement).all()
-    session.close()
     sys = CRUD(SysConfigs)
     action_configs = sys.read(session, conditions={"key": "action_configs"})
     session.close()
@@ -965,6 +968,7 @@ def get_device_configs(params: ModelGetDeviceConfig):
         order_by="action_index",
         ascending=True,
     )
+    session.close()
     return {
         "code": 0,
         "msg": "",
@@ -978,6 +982,7 @@ def device_config_detail(params: ModelGetDeviceConfigDetail):
     session = SqlQuery()
     configModel = CRUD(DeviceConfig)
     model = configModel.read(session, conditions={"id": action_id})
+    session.close()
     if model == None:
         return {"code": 1, "msg": "数据不存在", "data": None}
     return {"code": 0, "msg": "", "data": model}
@@ -998,6 +1003,7 @@ def device_config_detail_query():
     )
     if model == None:
         model = configModel.read(session, conditions={"tab_id": left_config})
+    session.close()
     return {"code": 0, "msg": "", "data": model}
 
 
@@ -1015,6 +1021,7 @@ def get_device_configs(params: ModelGetDeviceConfigDetail):
     if len(configArray) == 1:
         return {"code": 1, "msg": "请至少保留一个配置", "data": None}
     configModel.delete(session, obj_id=action_id)
+    session.close()
     return {"code": 0, "msg": "删除成功", "data": None}
 
 
@@ -1034,6 +1041,7 @@ def save_device_config(params: SaveDeviceConfig):
         # 走编辑逻辑
         kwargs = params.__dict__
         save_device_config = deviceConfig.update(session, obj_id=action_id, **kwargs)
+    session.close()
     return {"code": 0, "msg": "操作成功", "data": save_device_config}
 
 
@@ -1055,7 +1063,7 @@ def reset_config(params: ModelGetDeviceConfig):
         device_config = DeviceConfig(**data)
         session.add(device_config)
     session.commit()
-    # session.close()
+    session.close()
     return {"code": 0, "msg": "操作成功", "data": None}
 
 
@@ -1095,7 +1103,7 @@ def get_photo_records(page: int = 1, size: int = 5):
                 "items": list_item,
             }
         )
-    # session.close()
+    session.close()
     print("循环查询 完成 ", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
     return {
         "code": 0,
@@ -1113,7 +1121,7 @@ def get_last_photo_record():
         .order_by(desc("photo_create_time"))
     )
     result = session.exec(statement).first()
-    # session.close()
+    session.close()
     return {
         "code": 0,
         "msg": "",
@@ -1128,7 +1136,7 @@ def get_photo_record_detail(goods_art_no: str = None):
     session = SqlQuery()
     photos = CRUD(PhotoRecord)
     items = photos.read_all(session, conditions={"goods_art_no": goods_art_no})
-    # session.close()
+    session.close()
     return {
         "code": 0,
         "msg": "",
@@ -1138,11 +1146,15 @@ def get_photo_record_detail(goods_art_no: str = None):
 
 @app.post("/delect_goods_arts", description="通过货号删除记录")
 def delect_goods_arts(params: PhotoRecordDelete):
+    limit_path = "output/{}".format(
+                    time.strftime("%Y-%m-%d", time.localtime(time.time()))
+                )
+    handlerFolderDelete(limit_path,params.goods_art_nos,False)
     session = SqlQuery()
     photos = CRUD(PhotoRecord)
     for item in params.goods_art_nos:
         photos.deleteConditions(session, conditions={"goods_art_no": item})
-    # session.close()
+    session.close()
     return {
         "code": 0,
         "msg": "操作成功",
@@ -1190,6 +1202,7 @@ def get_sys_config(key: str = None):
             session.add(config)
             session.commit()  # 合并事务提交
             search_data = json.loads(sys_config_json.get("value"))
+    session.close()
     return {
         "code": 0,
         "msg": "",
@@ -1212,6 +1225,7 @@ def update_left_right_config(params: LeftRightParams):
     save_device_config = sysConfig.updateConditions(
         session, conditions={"key": "action_configs"}, **kwargs
     )
+    session.close()
     return {"code": 0, "msg": "操作成功", "data": None}
 
 
@@ -1224,6 +1238,7 @@ def update_record(params: RecordUpdate):
         return {"code": 1, "msg": "记录不存在", "data": None}
     kwargs = params.__dict__
     save_device_config = photoRecord.update(session, obj_id=params.id, **kwargs)
+    session.close()
     return {"code": 0, "msg": "操作成功", "data": save_device_config}
 
 
@@ -1239,6 +1254,7 @@ def save_sys_configs(params: SysConfigParams):
     save_device_config = sysConfig.updateConditions(
         session, conditions={"key": params.key}, **kwargs
     )
+    session.close()
     return {"code": 0, "msg": "操作成功", "data": save_device_config}
 
 
@@ -1256,7 +1272,6 @@ def insertEmptyLogoList(session):
     config = SysConfigs(**data)
     session.add(config)
     session.commit()
-    session.close()
     item = SysConfigs()
     item.key = "logo_configs"
     item.value = "[]"
@@ -1286,6 +1301,7 @@ def add_logo(params: LogoParams):
         return {"code": 1, "msg": "logo文件不存在", "data": None}
     logo_dir = "{}/data/logo/".format(os.getcwd()).replace("\\", "/")
     check_path(logo_dir)
+    session.close()
     fpath, fname = os.path.split(logo_path)
     logo_path_info = logo_dir + fname
     shutil.copy(logo_path, logo_path_info)  # 复制文件
@@ -1356,7 +1372,7 @@ def syncUserJsonConfigs(token):
                 }
             )
         batch_insert_sys_configs(session, configList)
-
+    session.close()
 
 @app.post("/sync_sys_configs", description="同步线上配置到本地")
 def sync_sys_configs(params: SyncLocalConfigs):
@@ -1392,6 +1408,7 @@ def sync_sys_configs(params: SyncLocalConfigs):
         # 同步本地到线上
         url = settings.DOMAIN + "/api/ai_image/camera_machine/update_all_user_configs"
         requests.post(url=url, headers=headers, data=data_json)
+    session.close()
     return {"code": 0, "msg": "操作成功", "data": None}
 
 
@@ -1442,4 +1459,5 @@ def sync_action_configs(params: SyncLocalConfigs):
                 )
     # 因为左右脚线上id可能会发生变化  所以需要重新同步一下本地得配置信息
     # syncUserJsonConfigs(hlm_token)
+    session.close()
     return {"code": 0, "msg": "操作成功", "data": None}

+ 4 - 2
python/custom_plugins/plugins_mode/detail_generate_base.py

@@ -20,7 +20,7 @@ import threading
 from concurrent.futures import ThreadPoolExecutor
 from concurrent.futures import TimeoutError as THTimeoutError
 from middleware import UnicornException
-
+from logger import logger
 # import math
 from PIL import ImageFont
 import settings
@@ -154,7 +154,7 @@ class DetailBase(object):
         if not os.path.exists(out_path):
             return
         try:
-            shutil.rmtree(out_path)
+            shutil.rmtree(out_path,onerror=settings.handle_remove_readonly)
         except BaseException as e:
             print("删除文件夹失败", e)
     def del_detail_longimage(self):
@@ -167,6 +167,8 @@ class DetailBase(object):
             shutil.rmtree(detail_image_path)
         except BaseException as e:
             print("删除详情页失败", e)
+            logger.info(f"detail_generae_base 抠图前目录删除出现问题:{str(e)}")
+
     def run_all(self):
         if self.template_name:
             self.out_put_dir = "{}/详情图-{}".format(

+ 6 - 5
python/databases.py

@@ -16,11 +16,12 @@ sqlite_url = f"sqlite:///{sqlite_file_name}"
 engine = create_engine(
     sqlite_url,
     echo=False,
-    connect_args={"check_same_thread": False},  # 允许多线程访问
-    pool_size=10,
-    max_overflow=20,
-    pool_timeout=30,
-    pool_recycle=1800,
+    connect_args={"check_same_thread": False},
+    pool_size=20,        # 增加基础连接池大小
+    max_overflow=30,     # 增加最大溢出连接数
+    pool_timeout=60,     # 保持合理的超时时间
+    pool_recycle=1800,   # 连接回收时间(秒)
+    pool_pre_ping=True,  # 检查连接有效性
 )
 
 

+ 2 - 2
python/mcu/DeviceControl.py

@@ -1705,7 +1705,6 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
                 print("action异常终止")
                 logger.info("action异常终止")
                 return
-            self.msg_type = "run_mcu_single"
             program_item.smart_shooter = smart_shooter
             await program_item.run(3)
             self.msg_type = "mcu"
@@ -1720,7 +1719,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
                 device_status=2,
             )
             self.action_state = 2
-            self.msg_type = msg_type
+            self.msg_type = "run_mcu_single"
             print("发送 run_mcu_signle消息", "执行完成")
             self.sendSocketMessage(
                 code=0,
@@ -1732,6 +1731,7 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             self.sendSocketMessage(
                 code=1, msg="未查询到重拍记录得配置信息,请确认", device_status=0
             )
+        self.action_state = 2
 
     async def only_take_photo(self, goods_art_no, image_index, record_id):
         await asyncio.sleep(0.1)

+ 75 - 88
python/mcu/RemoteControlV2.py

@@ -234,105 +234,92 @@ class RemoteControlV2(BaseClass):
         self.msg_type = "blue_tooth"
         self.photo_take_state = 2
 
-    async def handlerTakePhoto(self, smart_shooter=None):
+    async def handlerTakePhoto(self, smart_shooter=None,session=None,record=None):
         """处理单独拍照"""
         await asyncio.sleep(0.1)
-        print("开始单拍0")
-        session = SqlQuery()
-        crud = CRUD(PhotoRecord)
-        record = crud.read(session=session, order_by="id", ascending=False)
-        print("开始单拍0-读取数据库")
-        if record == None:
-            # 发送失败消息
+        print("开始单拍1")
+        if record.image_index == 19:
+            self.msg_type = "photo_take"
             self.sendSocketMessage(
                 code=1,
-                msg="单拍失败,请先输入货号或扫码进行组合拍摄",
+                msg="单拍失败,单个货号最多允许拍摄20张产品图",
                 data=None,
                 device_status=2,
             )
+            self.msg_type = "blue_tooth"
+            return
+        deviceConfig = CRUD(DeviceConfig)
+        deviceConfigData = deviceConfig.read(session=session, conditions={"id": record.action_id})
+        select_tab_id = deviceConfigData.tab_id
+        AllTabConfig = deviceConfig.read_all(session=session, conditions={"tab_id": select_tab_id})
+        action_id = 0
+        if AllTabConfig[len(AllTabConfig) - 1].take_picture == True:
+            action_id = AllTabConfig[0].id
         else:
-            print("开始单拍1")
-            if record.image_index == 19:
-                self.msg_type = "photo_take"
-                self.sendSocketMessage(
-                    code=1,
-                    msg="单拍失败,单个货号最多允许拍摄20张产品图",
-                    data=None,
-                    device_status=2,
-                )
-                self.msg_type = "blue_tooth"
-                return
-            deviceConfig = CRUD(DeviceConfig)
-            deviceConfigData = deviceConfig.read(session=session, conditions={"id": record.action_id})
-            select_tab_id = deviceConfigData.tab_id
-            AllTabConfig = deviceConfig.read_all(session=session, conditions={"tab_id": select_tab_id})
-            action_id = 0
-            if AllTabConfig[len(AllTabConfig) - 1].take_picture == True:
-                action_id = AllTabConfig[0].id
-            else:
-                action_id = AllTabConfig[len(AllTabConfig) - 1].id
-            image_index = record.image_index + 1
-            self.photo_take_state = 1
-            deviceConfig = CRUD(DeviceConfig)
-            deviceConfigData = deviceConfig.read(
-                session=session, conditions={"id": record.action_id}
-            )
-            select_tab_id = deviceConfigData.tab_id
-            AllTabConfig = deviceConfig.read_all(
-                session=session, conditions={"tab_id": select_tab_id}
-            )
-            action_id = 0
-            if AllTabConfig[len(AllTabConfig) - 1].take_picture == True:
-                action_id = AllTabConfig[0].id
+            action_id = AllTabConfig[len(AllTabConfig) - 1].id
+        image_index = record.image_index + 1
+        self.photo_take_state = 1
+        deviceConfig = CRUD(DeviceConfig)
+        deviceConfigData = deviceConfig.read(
+            session=session, conditions={"id": record.action_id}
+        )
+        select_tab_id = deviceConfigData.tab_id
+        AllTabConfig = deviceConfig.read_all(
+            session=session, conditions={"tab_id": select_tab_id}
+        )
+        action_id = 0
+        if AllTabConfig[len(AllTabConfig) - 1].take_picture == True:
+            action_id = AllTabConfig[0].id
+        else:
+            action_id = AllTabConfig[len(AllTabConfig) - 1].id
+        image_index = record.image_index + 1
+        self.photo_take_state = 1
+        state, record_id = await insert_photo_records(
+            record.image_deal_mode,
+            record.goods_art_no,
+            image_index,
+            action_id,
+        )
+        session.close()
+        print("开始单拍1-插入数据")
+        try:
+            if smart_shooter == None:
+                capture_one = DigiCam()
+                watch_dog = FileEventHandler()
+                if watch_dog.observer is None:
+                    captrure_folder_path = capture_one.getCaptureFolderPath()
+                    watch_dog.start_observer(captrure_folder_path)
+                watch_dog.goods_art_no = record.goods_art_no
+                watch_dog.image_index = image_index
+                print("开始单拍1-检查相机")
+                capture_one.run_capture_action("Capture")
+                print("开始单拍1-完成拍照")
             else:
-                action_id = AllTabConfig[len(AllTabConfig) - 1].id
-            image_index = record.image_index + 1
-            self.photo_take_state = 1
-            state, record_id = await insert_photo_records(
-                record.image_deal_mode,
-                record.goods_art_no,
-                image_index,
-                action_id,
-            )
-            print("开始单拍1-插入数据")
-            try:
-                if smart_shooter == None:
-                    capture_one = DigiCam()
-                    watch_dog = FileEventHandler()
-                    if watch_dog.observer is None:
-                        captrure_folder_path = capture_one.getCaptureFolderPath()
-                        watch_dog.start_observer(captrure_folder_path)
-                    watch_dog.goods_art_no = record.goods_art_no
-                    watch_dog.image_index = image_index
-                    print("开始单拍1-检查相机")
-                    capture_one.run_capture_action("Capture")
-                    print("开始单拍1-完成拍照")
-                else:
-                    loop = asyncio.get_event_loop()
-                    loop.create_task(
-                        smart_shooter.CameraShooter(
-                            msg_type="handler_take_picture",
-                            goods_art_no=record.goods_art_no,
-                            id=record_id,
-                        ),
-                        name="CameraShooter",
-                    )
-                await asyncio.sleep(1)
-                self.msg_type = "photo_take"
-                self.sendSocketMessage(
-                    code=0,
-                    msg="{} 执行完成~".format(
-                        "执行右脚程序"
-                        if record.image_deal_mode == 1
-                        else "执行左脚程序"
+                loop = asyncio.get_event_loop()
+                loop.create_task(
+                    smart_shooter.CameraShooter(
+                        msg_type="handler_take_picture",
+                        goods_art_no=record.goods_art_no,
+                        id=record_id,
                     ),
-                    data={"goods_art_no": record.goods_art_no, "id": record_id},
-                    device_status=2,
+                    name="CameraShooter",
                 )
-                self.msg_type = "blue_tooth"
-            except Exception as e:
-                print(f"错误:{e}")
-                self.sendSocketMessage(1, "处理失败,请重试", device_status=-1)
+            await asyncio.sleep(1)
+            self.msg_type = "photo_take"
+            self.sendSocketMessage(
+                code=0,
+                msg="{} 执行完成~".format(
+                    "执行右脚程序"
+                    if record.image_deal_mode == 1
+                    else "执行左脚程序"
+                ),
+                data={"goods_art_no": record.goods_art_no, "id": record_id},
+                device_status=2,
+            )
+            self.msg_type = "blue_tooth"
+        except Exception as e:
+            print(f"错误:{e}")
+            self.sendSocketMessage(1, "处理失败,请重试", device_status=-1)
         self.photo_take_state = 0
 
     async def analysis_received_data(self):

+ 8 - 5
python/service/base.py

@@ -8,7 +8,7 @@ import shutil
 from hashlib import sha256, md5
 import requests
 from datetime import datetime
-from settings import OUTPUT_DIR
+from settings import OUTPUT_DIR,handle_remove_readonly
 
 # 获取digicam的路径
 def check_install_path(other):
@@ -174,7 +174,7 @@ def get_image_mask(path):
 # 删除文件夹下的所有文件
 def remove_all_file(directory):
     try:
-        shutil.rmtree(directory)
+        shutil.rmtree(directory,onerror=handle_remove_readonly)
         os.makedirs(directory)
     except Exception as e:
         print(f'Failed to clear directory {directory}. Reason: {e}')
@@ -247,9 +247,12 @@ def check_move_goods_art_no_folder(path, goods_art_nos,limit_folder):
                 folder_list[goods_art_no] = folder_data
                 if not os.path.exists(f"{limit_folder}/{goods_art_no}"):
                     # 目标不存在
-                    folder_list[goods_art_no] = folder_data
-                    print("移动目录", folder_data["folder_path"], limit_folder)
-                    shutil.move(folder_data["folder_path"], limit_folder)
+                    try:
+                        shutil.move(folder_data["folder_path"], limit_folder)
+                        folder_list[goods_art_no] = folder_data
+                        print("移动目录", folder_data["folder_path"], limit_folder)
+                    except:
+                        continue
                 else:
                     # 如果希望覆盖
                     print(f"目标目录 {limit_folder}/{goods_art_no} 已存在,跳过移动")

+ 20 - 10
python/service/base_deal.py

@@ -23,7 +23,7 @@ import requests
 import copy, asyncio
 from settings import sendSocketMessage
 from utils.common import message_queue
-
+from logger import logger
 def sendAsyncMessage(msg="", goods_arts=[], status="",progress={}):
     """异步发送消息"""
     data = {
@@ -338,14 +338,22 @@ class BaseDealImage(object):
         is_image_deal_mode = 0
 
         # 删除目录再新建
-        if os.path.exists("{}/阴影图处理".format(folder_path)):
-            shutil.rmtree("{}/阴影图处理".format(folder_path))
+        try:
+          if os.path.exists("{}/阴影图处理".format(folder_path)):
+            shutil.rmtree("{}/阴影图处理".format(folder_path),onerror=settings.handle_remove_readonly)
+        except Exception as e:
+          print('An exception occurred')
+          logger.info(f"base deal 抠图前目录删除出现问题:{str(e)}")
 
         self.crate_all_folders(folder_path)
         print(
             "***************all_original_images*********************",
             all_original_images,
         )
+        is_flip_800image = settings.getSysConfigs(
+            "basic_configs", "is_flip_800image", 1
+        )
+        image_deal_mode = int(is_flip_800image)
         for image_dict in all_original_images:
             if windows:
                 if windows.state != 1:
@@ -369,13 +377,14 @@ class BaseDealImage(object):
                 f"*****************此处判断鞋子是否为左右脚====>{image_index}<======={is_image_deal_mode}=======>**********************"
             )
             # 此处判断鞋子是否为左右脚
-            if image_index == 1:
-                is_image_deal_mode = 0
-                print("开始识别左右脚=========>")
-                if OnePicDeal(self.token).check_shoe_is_right(
-                    image_path=original_move_bg_image_path
-                ):
-                    is_image_deal_mode = 1  # 1表示要镜像,0表示不做镜像
+            if image_deal_mode == 1:
+                if image_index == 1:
+                    is_image_deal_mode = 0
+                    print("开始识别左右脚=========>")
+                    if OnePicDeal(self.token).check_shoe_is_right(
+                        image_path=original_move_bg_image_path
+                    ):
+                        is_image_deal_mode = 1  # 1表示要镜像,0表示不做镜像
             print(
                 "*************************进行800image 生成********************************************"
             )
@@ -1169,6 +1178,7 @@ class BaseDealImage(object):
             order_by="id",
             ascending=True,
         )
+        session.close()
         if result:
             return result.goods_art_no, result.image_index, result.image_deal_mode
         else:

+ 7 - 5
python/service/deal_image.py

@@ -31,7 +31,7 @@ class DealImage(BaseDealImage):
     def list_dir(self, path):
         listdir = os.listdir(path)
         sort_result = sorted(listdir, key=lambda x: int(re.findall(r'^(\d+)_', x)[0]) if re.findall(r'^(\d+)_', x) else 0)
-        print("listdir   排序排序排序排序排序排序", sort_result)
+        # print("listdir   排序排序排序排序排序排序", sort_result)
         # return natsorted(listdir, alg=ns.PATH)
         return sort_result
     def get_date_time_original(self, file_path):
@@ -56,6 +56,7 @@ class DealImage(BaseDealImage):
             order_by="id",
             ascending=True,
         )
+        session.close()
         if result:
             return result.goods_art_no, result.image_index, result.image_deal_mode
         else:
@@ -319,14 +320,15 @@ class DealImage(BaseDealImage):
                     continue
 
                 all_original_images = os.listdir(_path)  # 遍历货号原始图文件夹
-                goods_pic_total = len(all_original_images)
-                if not all_original_images:
+                sort_result = sorted(all_original_images, key=lambda x: int(re.findall(r'((\d+))', x)[0]) if re.findall(r'((\d+))', x) else 0)
+                goods_pic_total = len(sort_result)
+                if not sort_result:
                     continue
-                image_file = all_original_images[0]  # 取第一个货号图
+                image_file = sort_result[0]  # 取第一个货号图
                 image_file_path = "{}/{}/原始图/{}".format(
                     dir_path, goods_art_no_folder, image_file
                 )
-
+                # print("\033[1;32;40m 需要处理的200images,路径: \033[0m",image_file_path,all_original_images,sort_result)
                 if not os.path.exists(
                         "{}/{}/200images".format(dir_path, goods_art_no_folder)
                 ):

+ 150 - 35
python/service/grenerate_main_image_test.py

@@ -350,7 +350,87 @@ class GeneratePic(object):
         time.sleep(3)
         if output_queue is not None:
             output_queue.put(True)
+    def paste_img(self,image, top_img, base="nw", value=(0, 0), ):
+            """
+            {
+                "command": "paste_img",
+                "im": 需要粘贴的图片
+                "pos": {"plugins_mode": "relative",  # pixel
+                        "base": "center",  # nw,nc,ne,ec ... 各个方向参考点
+                        "value": (100, 100),
+                        "percentage": (0.5, 0.5),
+                        },
+                "margins": (0, 0, 0, 0),  # 上下左右边距
+            }
+            """
+            value = (int(value[0]), int(value[1]))
+            # 处理默认值
+            base = "nw" if not base else base
+            top, down, left, right = 0, 0, 0, 0
+
+            # 基于右边,上下居中
+            if base == "ec" or base == "ce":
+                p_x = int(image.width - (top_img.width + value[0]))
+                p_y = int((image.height - top_img.height) / 2) + value[1]
+
+            # 基于顶部,左右居中
+            if base == "nc" or base == "cn":
+                # 顶部对齐
+                deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
+                    (image.height - top_img.height) / 2
+                )
+                p_x = deviation_x + value[0] + left
+                p_y = value[1]
+
+            # 基于右上角
+            if base == "en" or base == "ne":
+                p_x = int(image.width - (top_img.width + value[0])) + left
+                p_y = value[1]
+
+            # 基于左上角
+            if base == "nw" or base == "wn":
+                deviation_x, deviation_y = 0, 0
+                p_x, p_y = value
+
+            # 基于底部,左右居中
+            if base == "cs" or base == "sc":
+                deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
+                    (image.height - top_img.height) / 2
+                )
+
+                p_y = image.height - (top_img.height + value[1] + down)
+                p_x = deviation_x + value[0] + left
+
+            # 上下左右居中
+            if base == "center" or base == "cc":
+                deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
+                    (image.height - top_img.height) / 2
+                )
+                p_x = deviation_x + value[0] + left
+                p_y = deviation_y + value[1] + top
+
+            # 基于左下角
+            if base == "sw" or base == "ws":
+                # deviation_x, deviation_y = 0, int((img.height - img_1.height))
+                p_x = value[0] + left
+                p_y = image.height - (top_img.height + value[1] + down)
+
+            # 基于左边,上下居中
+            if base == "wc" or base == "cw":
+                p_x = value[0] + left
+                p_y = int((image.height - top_img.height) / 2) + value[1] + top
 
+            # 基于右下角
+            if base == "es" or base == "se":
+                p_x = int(image.width - (top_img.width + value[0])) + left
+                p_y = image.height - (top_img.height + value[1] + down) + top
+
+            try:
+                image.paste(top_img, box=(p_x, p_y), mask=top_img)
+            except:
+                image.paste(top_img, box=(p_x, p_y), mask=top_img.convert("RGBA"))
+
+            return image
     @time_it
     def run(
         self,
@@ -386,7 +466,10 @@ class GeneratePic(object):
             output_queue = kwargs["output_queue"]
         else:
             output_queue = None
-
+        # image_deal_mode = 0#不翻转图像
+        padding_800image = settings.getSysConfigs(
+            "basic_configs", "padding_800image", 100
+        )
         # ==========先进行剪切原图
         _s = time.time()
         orign_im = Image.open(image_path)  # 原始图
@@ -482,46 +565,70 @@ class GeneratePic(object):
         # 不生成主图时直接退出
         if not out_path:
             return True
-
-        # im_shadow.show()
-        # =====================主图物体的缩放依据大小
-        if max_box:
-            im_shadow = to_resize(_im=im_shadow, width=max_box[0], high=max_box[1])
-            cut_image = to_resize(_im=cut_image, width=max_box[0], high=max_box[1])
-        else:
-            if resize_mode is None:
-                im_shadow = to_resize(_im=im_shadow, width=1400, high=1400)
-                cut_image = to_resize(_im=cut_image, width=1400, high=1400)
-
-            elif resize_mode == 1:
-                im_shadow = to_resize(_im=im_shadow, width=1400, high=1400)
-                cut_image = to_resize(_im=cut_image, width=1400, high=1400)
-
-            elif resize_mode == 2:
-                # todo 兼容长筒靴等,将图片大小限制在一个指定的box内
-                im_shadow = to_resize(_im=im_shadow, width=650)
-                cut_image = to_resize(_im=cut_image, width=650)
-                # 再次检查需要约束缩小到一定高度,适应长筒靴
-                _im_x, _im_y = cut_image.size
-                if _im_y > 1400:
-                    im_shadow = to_resize(_im=im_shadow, high=1400)
-                    cut_image = to_resize(_im=cut_image, high=1400)
-
-                # if im_shadow.height <= im_shadow.width * 1.2:
-                #     im_shadow = to_resize(_im=im_shadow, width=650)
-                #     cut_image = to_resize(_im=cut_image, width=650)
-                # else:
-                #     im_shadow = to_resize(_im=im_shadow, high=1400)
-                #     cut_image = to_resize(_im=cut_image, high=1400)
-
         if image_deal_mode == 1:
             # 翻转
             im_shadow = im_shadow.transpose(Image.FLIP_LEFT_RIGHT)
             cut_image = cut_image.transpose(Image.FLIP_LEFT_RIGHT)
+        image_margin = int(padding_800image)
+        bg_size = (1600, 1600)
+        _offset_x, _offset_y = 0, 0
+        scale_rate = 1
+        # im_shadow.show()
+        # =====================主图物体的缩放依据大小
+        if image_margin is not None:
+            _bbox = cut_image.getbbox()
+            _x, _y = _bbox[0], _bbox[1]
+            _w, _h = _bbox[2] - _bbox[0], _bbox[3] - _bbox[1]
+            # 中心偏移量
+            offset_x, offset_y = _x - (cut_image.width - _w) / 2, _y - (cut_image.height - _h) / 2,
+            # print("中心偏移量:", offset_x, offset_y)
+            # 透明底最小矩形
+            scale_rate = self.get_scale(base_by_box=(bg_size[0] - image_margin * 2, bg_size[1] - image_margin * 2), image_size=(_w, _h))
+            # 计算缩放比例,以及顶点相对位置
+            # print("缩放比例:", scale_rate)
+            # 偏移量
+            _offset_x, _offset_y = offset_x * scale_rate, offset_y * scale_rate
+            # print("偏移量:", _offset_x, _offset_y)
+            # 阴影图缩放尺寸
+            cut_image = to_resize(_im=cut_image, width=cut_image.width * scale_rate)
+            im_shadow = to_resize(_im=im_shadow, width=im_shadow.width * scale_rate)
 
-        # 创建底层背景
-        image_bg = Image.new("RGB", (1600, 1600), (255, 255, 255))
+        else:
+            if max_box:
+                im_shadow = to_resize(_im=im_shadow, width=max_box[0], high=max_box[1])
+                cut_image = to_resize(_im=cut_image, width=max_box[0], high=max_box[1])
+            else:
+                size_defind = 1400
+                if resize_mode is None:
+                    im_shadow = to_resize(_im=im_shadow, width=size_defind, high=size_defind)
+                    cut_image = to_resize(_im=cut_image, width=size_defind, high=size_defind)
+
+                elif resize_mode == 1:
+                    im_shadow = to_resize(_im=im_shadow, width=size_defind, high=size_defind)
+                    cut_image = to_resize(_im=cut_image, width=size_defind, high=size_defind)
+
+                elif resize_mode == 2:
+                    # todo 兼容长筒靴等,将图片大小限制在一个指定的box内
+                    im_shadow = to_resize(_im=im_shadow, width=650)
+                    cut_image = to_resize(_im=cut_image, width=650)
+                    # 再次检查需要约束缩小到一定高度,适应长筒靴
+                    _im_x, _im_y = cut_image.size
+                    if _im_y > 1400:
+                        im_shadow = to_resize(_im=im_shadow, high=1400)
+                        cut_image = to_resize(_im=cut_image, high=1400)
+
+                    # if im_shadow.height <= im_shadow.width * 1.2:
+                    #     im_shadow = to_resize(_im=im_shadow, width=650)
+                    #     cut_image = to_resize(_im=cut_image, width=650)
+                    # else:
+                    #     im_shadow = to_resize(_im=im_shadow, high=1400)
+                    #     cut_image = to_resize(_im=cut_image, high=1400)
 
+
+        # 创建底层背景
+        image_bg = Image.new("RGB", bg_size, (255, 255, 255))
+        image_bg = self.paste_img(image=image_bg, top_img=im_shadow, base="cc", value=(_offset_x * -1, _offset_y * -1))
+        image_bg = self.paste_img(image=image_bg, top_img=cut_image, base="cc", value=(_offset_x * -1, _offset_y * -1))
         image_bg_x, image_bg_y = image_bg.size
         image_x, image_y = im_shadow.size
 
@@ -635,3 +742,11 @@ class GeneratePic(object):
         if output_queue is not None:
             output_queue.put(True)
         return True
+    def get_scale(self,base_by_box, image_size):
+        box_width, box_height = int(base_by_box[0]), int(base_by_box[1])
+        width, height = image_size[0], image_size[1]
+        if box_width / box_height < width / height:
+            scale = box_width / width
+        else:
+            scale = box_height / height
+        return scale

+ 4 - 1
python/service/match_and_cutout_mode_control/base_deal_image_v2.py

@@ -257,8 +257,11 @@ class BaseDealImage(object):
         is_image_deal_mode = 0
 
         # 删除目录再新建
-        if os.path.exists('{}/阴影图处理'.format(folder_path)):
+        try:
+          if os.path.exists('{}/阴影图处理'.format(folder_path)):
             shutil.rmtree('{}/阴影图处理'.format(folder_path))
+        except:
+          print('An exception occurred')
 
         self.crate_all_folders(folder_path)
 

+ 7 - 1
python/settings.py

@@ -7,6 +7,7 @@ import pillow_avif
 from utils.common import message_queue
 import hashlib
 from pathlib import Path
+import os,stat
 TIME_ZONE = pytz.timezone("Asia/Shanghai")
 from numpy import true_divide
 from databases import (
@@ -47,6 +48,7 @@ else:
             config = SysConfigs(**sys_config)
             session.add(config)
             session.commit()  # 合并事务提交
+session.close()
 # 初始化数据表---结束
 
 
@@ -65,6 +67,7 @@ def getSysConfigs(key, item, default=None):
     crud = CRUD(SysConfigs)
     one_item = crud.read(session, conditions={"key": key})
     config = json.loads(one_item.value)
+    session.close()
     if item == "image_out_format":
         default_format = config.get(item, default)
         if default_format == "" or default_format == None:
@@ -321,4 +324,7 @@ def calculate_md5(filepath):
 __output_dir = config.get("output_config", "output_dir")
 path_obj = Path(os.path.abspath(__output_dir))
 OUTPUT_DIR = path_obj.absolute()
-print("OUTPUT_DIR",__output_dir,OUTPUT_DIR)
+print("OUTPUT_DIR",__output_dir,OUTPUT_DIR)
+def handle_remove_readonly(func, path, exc):
+    os.chmod(path, stat.S_IWRITE)
+    func(path)

+ 5 - 1
python/sockets/connect_manager.py

@@ -27,7 +27,11 @@ class ConnectionManager:
     async def send_personal_message(self, message: str, websocket: WebSocket):
         '''向用户发送消息'''
         # await websocket.send_json(message)
-        await websocket.send_json(message)
+        try:
+          await websocket.send_json(message)
+        except Exception as e:
+          logger.info(f"socket 消息发送异常:{str(e)}")
+          await asyncio.sleep(0.001)
 
     async def broadcast(self, message: str):
         """广播消息"""

+ 97 - 15
python/sockets/message_handler.py

@@ -16,7 +16,8 @@ import settings
 from middleware import UnicornException
 from concurrent.futures import ThreadPoolExecutor
 from functools import partial
-
+from logger import logger
+import stat
 # 创建全局线程池
 executor = ThreadPoolExecutor(max_workers=4)
 async def handlerCutOut(
@@ -52,7 +53,43 @@ async def handlerCutOut(
         )
         await manager.send_personal_message(data, websocket)
 
-
+def handlerFolderDelete(limit_path,goods_art_no_arrays,is_write_txt_log):
+    check_path(limit_path)
+    move_folder_array = check_move_goods_art_no_folder(
+                "output", goods_art_no_arrays, limit_path
+            )
+    for goods_art_revice in goods_art_no_arrays:
+                cutout_goods = f"{limit_path}/{goods_art_revice}"
+                if os.path.exists(cutout_goods):
+                    # 寻找当前被扣图的货号在现有目录中是否存在,如果存在先删除
+                    # 重新执行抠图操作
+                    try:
+                        shutil.rmtree(cutout_goods, onerror=settings.handle_remove_readonly)
+                        del move_folder_array[goods_art_revice]
+                    except PermissionError as e:
+                        logger.info(f"抠图前目录删除出现问题:{str(e)};{goods_art_revice};{cutout_goods}")
+                        if is_write_txt_log:
+                            error_file_path = f"{cutout_goods}/异常说明-出现目录丢失或缺少图片请点开查看原因.txt"
+                            with open(error_file_path, 'w', encoding='utf-8') as f:
+                                f.write("目录删除失败\n")
+                                f.write(f"原因: 文件被占用或没有删除权限\n")
+                                f.write(f"错误信息: {str(e)}\n")
+                                f.write(f"时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
+                                f.write(f"请关闭可能正在使用此目录的程序后,为当前货号点击重拍后重试\n")
+                        else:
+                            raise UnicornException(f"目录检查出现问题:{str(e)},请关闭错误提示中的被占用文件")
+                    except Exception as e:
+                        logger.info(f"抠图前目录删除出现问题:{str(e)};{goods_art_revice};{cutout_goods}")
+                        if is_write_txt_log:
+                            error_file_path = f"{cutout_goods}/异常说明-出现目录丢失或缺少图片请点开查看原因.txt"
+                            with open(error_file_path, 'w', encoding='utf-8') as f:
+                                f.write("目录删除失败\n")
+                                f.write(f"原因: {str(e)}\n")
+                                f.write(f"时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
+                                f.write(f"请检查目录状态后,为当前货号点击重拍后重试\n")
+                        else:
+                            raise UnicornException(f"目录检查出现问题:{str(e)},请关闭错误提示中的被占用文件")
+    return move_folder_array                  
 # socket消息发送逻辑处理方法
 async def handlerSend(
     manager: ConnectionManager,
@@ -126,6 +163,9 @@ async def handlerSend(
                 device_ctrl.is_stop_action = True
             else:
                 print("动作没有执行,略过")
+                data = manager.jsonMessage(code=0, msg="执行终止", msg_type="run_mcu_stop")
+                await manager.send_personal_message(data, websocket)
+                return
         case "run_mcu":
             msg_type = "run_mcu"
             action_info = data.get("action", "执行左脚程序")
@@ -180,6 +220,7 @@ async def handlerSend(
                 ),
                 name="run_mcu_config",
             )
+            session.close()
         case "run_mcu_single":
             device_ctrl = DeviceControl(
                 websocket_manager=manager, smart_shooter=smart_shooter
@@ -198,8 +239,33 @@ async def handlerSend(
             blue_tooth = BlueToothMode(
                 websocket_manager=manager, smart_shooter=smart_shooter
             )
+            session = SqlQuery()
+            crud = CRUD(PhotoRecord)
+            record = crud.read(session=session, order_by="id", ascending=False)
+            if record == None:
+            # 发送失败消息
+                data = manager.jsonMessage(
+                            code=1,
+                            msg="单拍失败,请先输入货号或扫码进行组合拍摄",
+                            msg_type="handler_take_picture",
+                        )
+                await manager.send_personal_message(data, websocket)
+                return
+            try:
+                limit_path = "output/{}".format(
+                    time.strftime("%Y-%m-%d", time.localtime(time.time()))
+                )
+                move_folder_array = handlerFolderDelete(limit_path,[record.goods_art_no],False)
+            except UnicornException as e:
+                data = manager.jsonMessage(
+                        code=1,
+                        msg=e.msg,
+                        msg_type="handler_take_picture",
+                    )
+                await manager.send_personal_message(data, websocket)
+                return
             loop.create_task(
-                blue_tooth.remote_control_v2.handlerTakePhoto(smart_shooter),
+                blue_tooth.remote_control_v2.handlerTakePhoto(smart_shooter,session,record),
                 name="run_mcu_config",
             )
             await asyncio.sleep(2.5)
@@ -248,6 +314,7 @@ async def handlerSend(
                 ),
                 name="run_mcu_config_single",
             )
+            session.close()
         case "get_deviation":
             device_ctrl = DeviceControl(
                 websocket_manager=manager, smart_shooter=smart_shooter
@@ -347,6 +414,20 @@ async def handlerSend(
             # 兼容主图测试
             id = data.get("id", 0)
             goods_art_no = data.get("goods_art_no", "")
+            if goods_art_no:
+                try:
+                    limit_path = "output/{}".format(
+                        time.strftime("%Y-%m-%d", time.localtime(time.time()))
+                    )
+                    move_folder_array = handlerFolderDelete(limit_path,[goods_art_no],False)
+                except UnicornException as e:
+                    data = manager.jsonMessage(
+                            code=1,
+                            msg=e.msg,
+                            msg_type="smart_shooter_photo_take",
+                        )
+                    await manager.send_personal_message(data, websocket)
+                    return
             is_af = True
             loop.create_task(
                 smart_shooter.CameraShooter(
@@ -385,6 +466,7 @@ async def handlerSend(
                 ),
                 name="sendCommand",
             )
+            session.close()
         case "segment_progress":
             msg_type = "segment_progress"
             obj = None
@@ -396,20 +478,19 @@ async def handlerSend(
             limit_path = "{}/{}".format(settings.OUTPUT_DIR,
                 time.strftime("%Y-%m-%d", time.localtime(time.time()))
             )
-            check_path(limit_path)
+            try:
+                move_folder_array = handlerFolderDelete(limit_path,goods_art_no_arrays,True)
+            except UnicornException as e:
+                data = manager.jsonMessage(
+                        code=1,
+                        msg=e.msg,
+                        msg_type=msg_type,
+                    )
+                await manager.send_personal_message(data, websocket)
+                return
             # 该数组表示是否需要后面的移动文件夹操作,减少重复抠图,提升抠图时间和速度
-            move_folder_array = check_move_goods_art_no_folder(
-                settings.OUTPUT_DIR, goods_art_no_arrays, limit_path
-            )
-            for goods_art_revice in goods_art_no_arrays:
-                cutout_goods = f"{limit_path}/{goods_art_revice}"
-                if os.path.exists(cutout_goods):
-                    # 寻找当前被扣图的货号在现有目录中是否存在,如果存在先删除
-                    # 重新执行抠图操作
-                    shutil.rmtree(cutout_goods)
-                    del move_folder_array[goods_art_revice]
+            session = SqlQuery()
             for goods_art_no in goods_art_no_arrays:
-                session = SqlQuery()
                 pr = CRUD(PhotoRecord)
                 images = pr.read_all(session, conditions={"goods_art_no": goods_art_no})
                 if not images:
@@ -454,6 +535,7 @@ async def handlerSend(
                         )
                         await manager.send_personal_message(data, websocket)
                         return
+            session.close()
             # try:
             cutOutMode = (
                 "1"

+ 1 - 1
python/sockets/socket_server.py

@@ -35,7 +35,7 @@ async def updateDataRecord(PhotoFilename, id):
         # 走编辑逻辑
         record_model.updateConditions(session, conditions={"id": id}, **data)
         print(f"smart shooter 拍照记录更新成功,记录id:{id}")
-
+    session.close()
 
 @app.websocket("/ws")
 async def websocket_endpoint(websocket: WebSocket):