This commit is contained in:
ksenia_mikhailova 2025-06-24 17:04:22 +03:00
parent 875103603c
commit 25ee7e317d
4 changed files with 181 additions and 61 deletions

View File

@ -6,12 +6,21 @@ from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWebEngineWidgets import QWebEngineView
from enums import KompasCommand from enums import KompasCommand
from logger import logger
from parser.main import KompasDocumentParser from parser.main import KompasDocumentParser
class PythonEventEmitter(QObject): class PythonEventEmitter(QObject):
onEvent = pyqtSignal(str, "QVariantMap") # событие: тип + данные onEvent = pyqtSignal(str, "QVariantMap") # событие: тип + данные
def format_response(success: bool, data=None, error: str = None):
return json.dumps(
{"status": "success" if success else "error", "data": data, "error": error},
ensure_ascii=False,
)
class PythonJSBridge(QObject): class PythonJSBridge(QObject):
def __init__(self, browser: QWebEngineView): def __init__(self, browser: QWebEngineView):
super().__init__() super().__init__()
@ -24,26 +33,34 @@ class PythonJSBridge(QObject):
self.channel.registerObject("pyjs", self) self.channel.registerObject("pyjs", self)
self.channel.registerObject("pyjs_events", self.emitter) self.channel.registerObject("pyjs_events", self.emitter)
self.kompas = KompasDocumentParser() self.kompas = KompasDocumentParser()
@pyqtSlot(str, str, result=str) @pyqtSlot(str, str, result=str)
def callFromJS(self, command: str, data_json: str): def callFromJS(self, command: str, data_json: str):
print(f"[Python] Получена команда: {command}") logger.info(f"[Python] Получена команда: {command}")
data = None
if command == KompasCommand.OPEN_KOMPAS: try:
data = self.kompas.get_open_documents() if command == KompasCommand.OPEN_KOMPAS:
if command == KompasCommand.GET_AVAILABLE_ACTIONS: data = self.kompas.get_open_documents()
data = self.kompas.get_available_actions() elif command == KompasCommand.GET_AVAILABLE_ACTIONS:
if command == KompasCommand.IGES: data = self.kompas.get_available_actions()
data = self.kompas.save_to_iges() elif command == KompasCommand.IGES:
if command == KompasCommand.EXPORT_RASTER: data = self.kompas.save_to_iges() # <-- твой метод
data = self.kompas.export_to_raster() elif command == KompasCommand.EXPORT_RASTER:
if command == KompasCommand.PROJECT_SUPPORT: data = self.kompas.export_to_raster()
data = self.kompas.create_drawing_for_parts() elif command == KompasCommand.PROJECT_SUPPORT:
if command == KompasCommand.STATS: data = self.kompas.create_drawing_for_parts()
data = self.kompas.collect_statistics() elif command == KompasCommand.STATS:
return json.dumps(data, ensure_ascii=False) data = self.kompas.collect_statistics()
else:
return format_response(False, error=f"Неизвестная команда: {command}")
return format_response(True, data=data)
except Exception as e:
logger.error(f"Ошибка при выполнении команды {command}: {e}", exc_info=True)
return format_response(False, error=str(e))
def send_event_to_js(self, event_type: str, data: dict): def send_event_to_js(self, event_type: str, data: dict):
self.emitter.onEvent.emit(event_type, data) self.emitter.onEvent.emit(event_type, data)

29
logger.py Normal file
View File

@ -0,0 +1,29 @@
# logger.py
import logging
import os
# Создаем корневой логгер
logger = logging.getLogger('parser')
logger.setLevel(logging.DEBUG) # Уровень по умолчанию: DEBUG и выше
# Проверяем, существует ли папка logs
logs_dir = "logs"
if not os.path.exists(logs_dir):
os.makedirs(logs_dir)
# Форматтер для записей в логе
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s'
)
# Обработчик для вывода в консоль
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# Обработчик для записи в файл
file_handler = logging.FileHandler(os.path.join(logs_dir, 'parser.log'), encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

24
logs/parser.log Normal file
View File

@ -0,0 +1,24 @@
2025-06-24 16:48:58,547 - parser - INFO - main - save_to_iges - 244 - Начинаем сохранение документов в формате IGES...
2025-06-24 16:48:59,394 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Проф труба
2025-06-24 16:49:00,892 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Сборка изделий
2025-06-24 16:49:02,333 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Список деталей
2025-06-24 16:49:03,686 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Nema 17
2025-06-24 16:49:04,310 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Квадрат
2025-06-24 16:49:04,842 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Цилиндр
2025-06-24 16:49:48,697 - parser - INFO - main - save_to_iges - 244 - Начинаем сохранение документов в формате IGES...
2025-06-24 16:49:49,345 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Проф труба
2025-06-24 16:49:49,600 - parser - INFO - main - save_to_iges - 281 - Успешно сохранено: C:\Users\user\Desktop\ЗН 999 test\3D Модель\igs\Проф труба.igs
2025-06-24 16:49:50,167 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Сборка изделий
2025-06-24 16:49:50,554 - parser - INFO - main - save_to_iges - 281 - Успешно сохранено: C:\Users\user\Desktop\ЗН 999 test\3D Модель\igs\Сборка изделий.igs
2025-06-24 16:49:50,947 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Список деталей
2025-06-24 16:49:51,482 - parser - INFO - main - save_to_iges - 281 - Успешно сохранено: C:\Users\user\Desktop\ЗН 999 test\3D Модель\igs\Список деталей.igs
2025-06-24 16:49:52,346 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Nema 17
2025-06-24 16:49:52,485 - parser - INFO - main - save_to_iges - 281 - Успешно сохранено: C:\Users\user\Desktop\ЗН 999 test\3D Модель\igs\Nema 17.igs
2025-06-24 16:49:52,986 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Квадрат
2025-06-24 16:49:53,124 - parser - INFO - main - save_to_iges - 281 - Успешно сохранено: C:\Users\user\Desktop\ЗН 999 test\3D Модель\igs\Квадрат.igs
2025-06-24 16:49:53,781 - parser - INFO - main - save_to_iges - 264 - Попытка сохранить: Цилиндр
2025-06-24 16:49:53,923 - parser - INFO - main - save_to_iges - 281 - Успешно сохранено: C:\Users\user\Desktop\ЗН 999 test\3D Модель\igs\Цилиндр.igs
2025-06-24 17:00:05,175 - parser - INFO - bridge - callFromJS - 41 - [Python] Получена команда: open_kompas
2025-06-24 17:00:09,976 - parser - INFO - bridge - callFromJS - 41 - [Python] Получена команда: open_kompas
2025-06-24 17:01:37,671 - parser - INFO - bridge - callFromJS - 41 - [Python] Получена команда: open_kompas
2025-06-24 17:01:40,023 - parser - INFO - bridge - callFromJS - 41 - [Python] Получена команда: open_kompas

View File

@ -1,10 +1,11 @@
import os import os
import traceback import traceback
import pythoncom import time
from win32com.client import Dispatch, gencache from win32com.client import Dispatch, gencache
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
from enums import KompasCommand from enums import KompasCommand
from logger import logger
ids = { ids = {
"api_5": "{0422828C-F174-495E-AC5D-D31014DBBE87}", "api_5": "{0422828C-F174-495E-AC5D-D31014DBBE87}",
@ -58,6 +59,23 @@ class KompasDocumentParser:
traceback.print_exc() # <-- Выводит номер строки, файл и стек traceback.print_exc() # <-- Выводит номер строки, файл и стек
raise RuntimeError(f"Ошибка при подключении к КОМПАС: {e}") raise RuntimeError(f"Ошибка при подключении к КОМПАС: {e}")
def get_export_path(self, doc_path: str, doc_name: str, ext: str) -> str:
"""
Формирует путь для экспорта файла по шаблону:
- Создаёт подпапку `ext` внутри `doc_path`
- Возвращает полный путь к файлу `<doc_name>.<ext>`
"""
output_dir = os.path.join(doc_path, ext)
filename = f"{doc_name}.{ext}"
full_path = os.path.join(output_dir, filename)
return full_path
def prepare_export_path(self, doc_path: str, doc_name: str, ext: str) -> str:
full_path = self.get_export_path(doc_path, doc_name, ext)
os.makedirs(os.path.dirname(full_path), exist_ok=True)
return full_path
def get_open_documents(self): def get_open_documents(self):
"""Возвращает список информации о всех открытых документах""" """Возвращает список информации о всех открытых документах"""
@ -82,7 +100,7 @@ class KompasDocumentParser:
print(f"Ошибка при обработке документа #{i + 1}: {e}") print(f"Ошибка при обработке документа #{i + 1}: {e}")
return documents return documents
def create_drawing_for_parts(self): def create_drawing_for_parts(self):
""" """
Создание чертежей для всех уникальных деталей из открытых деталей и сборок. Создание чертежей для всех уникальных деталей из открытых деталей и сборок.
@ -104,13 +122,13 @@ class KompasDocumentParser:
continue continue
doc_path = doc.Path doc_path = doc.Path
# Сохранение чертежа # Сохранение чертежа
output_dir = os.path.join(doc_path, saving_path.strip("/")) output_dir = os.path.join(doc_path, saving_path.strip("/"))
if not os.path.exists(output_dir): if not os.path.exists(output_dir):
os.makedirs(output_dir) os.makedirs(output_dir)
print(f"Создана папка: {output_dir}") print(f"Создана папка: {output_dir}")
doc_type = doc.DocumentType doc_type = doc.DocumentType
if doc_type not in allowed_types: if doc_type not in allowed_types:
continue continue
@ -124,7 +142,7 @@ class KompasDocumentParser:
if not os.path.exists(output_dir): if not os.path.exists(output_dir):
os.makedirs(output_dir) os.makedirs(output_dir)
print(f"Создана папка: {output_dir}") print(f"Создана папка: {output_dir}")
doc_3d = self.api7_module.IKompasDocument3D(doc) doc_3d = self.api7_module.IKompasDocument3D(doc)
doc_components = [] doc_components = []
@ -154,7 +172,9 @@ class KompasDocumentParser:
print(f"Создаём чертёж для: {component.Name}") print(f"Создаём чертёж для: {component.Name}")
# Создаём новый чертёж # Создаём новый чертёж
c_doc = self.application.Documents.Add(self.constants.ksDocumentDrawing) c_doc = self.application.Documents.Add(
self.constants.ksDocumentDrawing
)
c_layout = c_doc.LayoutSheets c_layout = c_doc.LayoutSheets
c_sheet = c_layout.Item(0) c_sheet = c_layout.Item(0)
c_sheet.Format.Format = self.constants.ksFormatA3 c_sheet.Format.Format = self.constants.ksFormatA3
@ -190,7 +210,6 @@ class KompasDocumentParser:
20, 20,
) )
filename = "_".join( filename = "_".join(
filter( filter(
None, None,
@ -221,9 +240,12 @@ class KompasDocumentParser:
def save_to_iges(self): def save_to_iges(self):
"""Сохраняет открытые 3D-документы (детали/сборки) в формате IGES""" """Сохраняет открытые 3D-документы (детали/сборки) в формате IGES"""
print("Начинаем сохранение документов в формате IGES...") logger.info("Начинаем сохранение документов в формате IGES...")
result = {"result": []}
av_actions = self.get_available_actions() av_actions = self.get_available_actions()
docs_collection = self.application.Documents docs_collection = self.application.Documents
allowed_types = av_actions[KompasCommand.IGES]["allowed_types"]
for i in range(docs_collection.Count): for i in range(docs_collection.Count):
try: try:
@ -232,13 +254,13 @@ class KompasDocumentParser:
continue continue
doc_type = doc.DocumentType doc_type = doc.DocumentType
if doc_type not in av_actions[KompasCommand.IGES]["allowed_types"]: if doc_type not in allowed_types:
continue continue
doc.Active = True doc.Active = True
doc_path = doc.Path doc_path = doc.Path
doc_name = "-".join(doc.Name.split(".")[:-1]) doc_name = "-".join(doc.Name.split(".")[:-1])
print(f"Попытка сохранить: {doc_name}") logger.info(f"Попытка сохранить: {doc_name}")
# Получаем 3D-документ через API v5 # Получаем 3D-документ через API v5
doc_api5 = self.api5.ActiveDocument3D() doc_api5 = self.api5.ActiveDocument3D()
@ -252,34 +274,48 @@ class KompasDocumentParser:
save_params.Init() save_params.Init()
save_params.format = self.constants.ksConverterToIGES save_params.format = self.constants.ksConverterToIGES
ext = "igs" full_path = self.prepare_export_path(doc_path, doc_name, "igs")
output_dir = os.path.join(doc_path, ext) export_success = doc_api5.SaveAsToAdditionFormat(full_path, save_params)
filename = f"{doc_name}.{ext}" # Добавляем информацию о результате в массив
full_path = os.path.join(output_dir, filename) logger.info(f"Успешно сохранено: {full_path}")
result["result"].append(
{
"file": full_path,
"success": bool(export_success),
"document_name": doc_name,
"document_type": doc_type,
"timestamp": time.time(),
}
)
# Создаём директорию, если её нет if not export_success:
if not os.path.exists(output_dir): logger.error(f"Не удалось сохранить: {full_path}")
os.makedirs(output_dir)
# Сохраняем файл
result = doc_api5.SaveAsToAdditionFormat(full_path, save_params)
if result:
print(f"[OK] Сохранено: {full_path}")
else:
print(f"[ERROR] Не удалось сохранить: {full_path}")
except Exception as e: except Exception as e:
print(f"[ERROR] Ошибка при обработке документа #{i + 1}: {e}") logger.error(
traceback.print_exc() f"Ошибка при обработке документа #{i + 1}: {e}",
exc_info=True,
)
result["result"].append(
{
"file": None,
"success": False,
"error": str(e),
"document_index": i + 1,
"timestamp": time.time(),
}
)
return result
def collect_statistics(self): def collect_statistics(self):
"""Сбор статистики по элементам, гибам и сваркам в активном документе""" """Сбор статистики по элементам, гибам и сваркам в активном документе"""
print("Начинаем сбор статистики по элементам...") print("Начинаем сбор статистики по элементам...")
# Получаем доступные типы из разрешённых действий # Получаем доступные типы из разрешённых действий
av_actions = self.get_available_actions() av_actions = self.get_available_actions()
allowed_types = av_actions[KompasCommand.STATS]["allowed_types"] allowed_types = av_actions[KompasCommand.STATS]["allowed_types"]
docs_collection = self.application.Documents docs_collection = self.application.Documents
for i in range(docs_collection.Count): for i in range(docs_collection.Count):
@ -329,10 +365,12 @@ class KompasDocumentParser:
macro = self.api7_module.IMacroObject3D(drawing_context) macro = self.api7_module.IMacroObject3D(drawing_context)
sub_features = macro.Owner.SubFeatures(1, True, False) or [] sub_features = macro.Owner.SubFeatures(1, True, False) or []
for item in sub_features: for item in sub_features:
if isinstance(item, self.api7_module.IUserDesignationCompObj): if isinstance(
item, self.api7_module.IUserDesignationCompObj
):
welding.append(item) welding.append(item)
except Exception as e: except Exception as e:
print('Ошибка в DrawingContext:', e) print("Ошибка в DrawingContext:", e)
try: try:
doc_parts = self.api7_module.IParts7(part.Parts) doc_parts = self.api7_module.IParts7(part.Parts)
@ -343,7 +381,7 @@ class KompasDocumentParser:
look_features(element) look_features(element)
find_el(element) find_el(element)
except Exception as e: except Exception as e:
print('Ошибка в Parts:', e) print("Ошибка в Parts:", e)
if doc_type == self.constants.ksDocumentAssembly: if doc_type == self.constants.ksDocumentAssembly:
find_el(top_part) find_el(top_part)
@ -362,43 +400,55 @@ class KompasDocumentParser:
for e in elements: for e in elements:
for key in sorted_stats.keys(): for key in sorted_stats.keys():
if key == 'Name': if key == "Name":
value = f"{getattr(e, key)}, масса {round(getattr(e, 'Mass'), 3)}" value = f"{getattr(e, key)}, масса {round(getattr(e, 'Mass'), 3)}"
sorted_stats[key][value] = sorted_stats[key].get(value, 0) + 1 sorted_stats[key][value] = (
elif key == 'Area': sorted_stats[key].get(value, 0) + 1
mass_inertial_params = self.api7_module.IMassInertiaParam7(e) )
elif key == "Area":
mass_inertial_params = self.api7_module.IMassInertiaParam7(
e
)
area = mass_inertial_params.Area * 0.0001 # м² area = mass_inertial_params.Area * 0.0001 # м²
material = getattr(e, 'Material', 'Неизвестно') material = getattr(e, "Material", "Неизвестно")
key_area = f"Площадь {material}, м²:" key_area = f"Площадь {material}, м²:"
sorted_stats[key][key_area] = round(sorted_stats[key].get(key_area, 0) + area, 6) sorted_stats[key][key_area] = round(
sorted_stats[key].get(key_area, 0) + area, 6
)
else: else:
value = getattr(e, key) value = getattr(e, key)
sorted_stats[key][value] = sorted_stats[key].get(value, 0) + 1 sorted_stats[key][value] = (
sorted_stats[key].get(value, 0) + 1
)
sorted_stats['Area']['Total'] = sum(sorted_stats['Area'].values()) sorted_stats["Area"]["Total"] = sum(sorted_stats["Area"].values())
welding_key = "Welding" welding_key = "Welding"
for w in welding: for w in welding:
if welding_key not in sorted_stats: if welding_key not in sorted_stats:
sorted_stats[welding_key] = {} sorted_stats[welding_key] = {}
w_name_split = w.Name.split('-') w_name_split = w.Name.split("-")
w_len = w_name_split[-1].split('@')[0] w_len = w_name_split[-1].split("@")[0]
sorted_stats[welding_key][w.Name] = w_len sorted_stats[welding_key][w.Name] = w_len
if welding_key in sorted_stats: if welding_key in sorted_stats:
def float_f(n): def float_f(n):
try: try:
return float(n) return float(n)
except: except:
return 0 return 0
total_length = sum(float_f(v) for v in sorted_stats[welding_key].values())
total_length = sum(
float_f(v) for v in sorted_stats[welding_key].values()
)
sorted_stats[welding_key]["Total"] = total_length sorted_stats[welding_key]["Total"] = total_length
for section in sorted_stats: for section in sorted_stats:
print(section) print(section)
for name, value in sorted_stats[section].items(): for name, value in sorted_stats[section].items():
print(f"{name} -- {value}") print(f"{name} -- {value}")
print('-----') print("-----")
except Exception as e: except Exception as e:
print(f"[ERROR] Ошибка при обработке документа #{i + 1}: {e}") print(f"[ERROR] Ошибка при обработке документа #{i + 1}: {e}")