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 enums import KompasCommand
from logger import logger
from parser.main import KompasDocumentParser
class PythonEventEmitter(QObject):
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):
def __init__(self, browser: QWebEngineView):
super().__init__()
@ -24,26 +33,34 @@ class PythonJSBridge(QObject):
self.channel.registerObject("pyjs", self)
self.channel.registerObject("pyjs_events", self.emitter)
self.kompas = KompasDocumentParser()
@pyqtSlot(str, str, result=str)
def callFromJS(self, command: str, data_json: str):
print(f"[Python] Получена команда: {command}")
data = None
if command == KompasCommand.OPEN_KOMPAS:
data = self.kompas.get_open_documents()
if command == KompasCommand.GET_AVAILABLE_ACTIONS:
data = self.kompas.get_available_actions()
if command == KompasCommand.IGES:
data = self.kompas.save_to_iges()
if command == KompasCommand.EXPORT_RASTER:
data = self.kompas.export_to_raster()
if command == KompasCommand.PROJECT_SUPPORT:
data = self.kompas.create_drawing_for_parts()
if command == KompasCommand.STATS:
data = self.kompas.collect_statistics()
return json.dumps(data, ensure_ascii=False)
logger.info(f"[Python] Получена команда: {command}")
try:
if command == KompasCommand.OPEN_KOMPAS:
data = self.kompas.get_open_documents()
elif command == KompasCommand.GET_AVAILABLE_ACTIONS:
data = self.kompas.get_available_actions()
elif command == KompasCommand.IGES:
data = self.kompas.save_to_iges() # <-- твой метод
elif command == KompasCommand.EXPORT_RASTER:
data = self.kompas.export_to_raster()
elif command == KompasCommand.PROJECT_SUPPORT:
data = self.kompas.create_drawing_for_parts()
elif command == KompasCommand.STATS:
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):
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 traceback
import pythoncom
import time
from win32com.client import Dispatch, gencache
from PIL import Image, ImageDraw, ImageFont
from enums import KompasCommand
from logger import logger
ids = {
"api_5": "{0422828C-F174-495E-AC5D-D31014DBBE87}",
@ -58,6 +59,23 @@ class KompasDocumentParser:
traceback.print_exc() # <-- Выводит номер строки, файл и стек
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):
"""Возвращает список информации о всех открытых документах"""
@ -82,7 +100,7 @@ class KompasDocumentParser:
print(f"Ошибка при обработке документа #{i + 1}: {e}")
return documents
def create_drawing_for_parts(self):
"""
Создание чертежей для всех уникальных деталей из открытых деталей и сборок.
@ -104,13 +122,13 @@ class KompasDocumentParser:
continue
doc_path = doc.Path
# Сохранение чертежа
output_dir = os.path.join(doc_path, saving_path.strip("/"))
if not os.path.exists(output_dir):
os.makedirs(output_dir)
print(f"Создана папка: {output_dir}")
doc_type = doc.DocumentType
if doc_type not in allowed_types:
continue
@ -124,7 +142,7 @@ class KompasDocumentParser:
if not os.path.exists(output_dir):
os.makedirs(output_dir)
print(f"Создана папка: {output_dir}")
doc_3d = self.api7_module.IKompasDocument3D(doc)
doc_components = []
@ -154,7 +172,9 @@ class KompasDocumentParser:
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_sheet = c_layout.Item(0)
c_sheet.Format.Format = self.constants.ksFormatA3
@ -190,7 +210,6 @@ class KompasDocumentParser:
20,
)
filename = "_".join(
filter(
None,
@ -221,9 +240,12 @@ class KompasDocumentParser:
def save_to_iges(self):
"""Сохраняет открытые 3D-документы (детали/сборки) в формате IGES"""
print("Начинаем сохранение документов в формате IGES...")
logger.info("Начинаем сохранение документов в формате IGES...")
result = {"result": []}
av_actions = self.get_available_actions()
docs_collection = self.application.Documents
allowed_types = av_actions[KompasCommand.IGES]["allowed_types"]
for i in range(docs_collection.Count):
try:
@ -232,13 +254,13 @@ class KompasDocumentParser:
continue
doc_type = doc.DocumentType
if doc_type not in av_actions[KompasCommand.IGES]["allowed_types"]:
if doc_type not in allowed_types:
continue
doc.Active = True
doc_path = doc.Path
doc_name = "-".join(doc.Name.split(".")[:-1])
print(f"Попытка сохранить: {doc_name}")
logger.info(f"Попытка сохранить: {doc_name}")
# Получаем 3D-документ через API v5
doc_api5 = self.api5.ActiveDocument3D()
@ -252,34 +274,48 @@ class KompasDocumentParser:
save_params.Init()
save_params.format = self.constants.ksConverterToIGES
ext = "igs"
output_dir = os.path.join(doc_path, ext)
filename = f"{doc_name}.{ext}"
full_path = os.path.join(output_dir, filename)
full_path = self.prepare_export_path(doc_path, doc_name, "igs")
export_success = doc_api5.SaveAsToAdditionFormat(full_path, save_params)
# Добавляем информацию о результате в массив
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 os.path.exists(output_dir):
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}")
if not export_success:
logger.error(f"Не удалось сохранить: {full_path}")
except Exception as e:
print(f"[ERROR] Ошибка при обработке документа #{i + 1}: {e}")
traceback.print_exc()
logger.error(
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):
"""Сбор статистики по элементам, гибам и сваркам в активном документе"""
print("Начинаем сбор статистики по элементам...")
# Получаем доступные типы из разрешённых действий
av_actions = self.get_available_actions()
allowed_types = av_actions[KompasCommand.STATS]["allowed_types"]
docs_collection = self.application.Documents
for i in range(docs_collection.Count):
@ -329,10 +365,12 @@ class KompasDocumentParser:
macro = self.api7_module.IMacroObject3D(drawing_context)
sub_features = macro.Owner.SubFeatures(1, True, False) or []
for item in sub_features:
if isinstance(item, self.api7_module.IUserDesignationCompObj):
if isinstance(
item, self.api7_module.IUserDesignationCompObj
):
welding.append(item)
except Exception as e:
print('Ошибка в DrawingContext:', e)
print("Ошибка в DrawingContext:", e)
try:
doc_parts = self.api7_module.IParts7(part.Parts)
@ -343,7 +381,7 @@ class KompasDocumentParser:
look_features(element)
find_el(element)
except Exception as e:
print('Ошибка в Parts:', e)
print("Ошибка в Parts:", e)
if doc_type == self.constants.ksDocumentAssembly:
find_el(top_part)
@ -362,43 +400,55 @@ class KompasDocumentParser:
for e in elements:
for key in sorted_stats.keys():
if key == 'Name':
if key == "Name":
value = f"{getattr(e, key)}, масса {round(getattr(e, 'Mass'), 3)}"
sorted_stats[key][value] = sorted_stats[key].get(value, 0) + 1
elif key == 'Area':
mass_inertial_params = self.api7_module.IMassInertiaParam7(e)
sorted_stats[key][value] = (
sorted_stats[key].get(value, 0) + 1
)
elif key == "Area":
mass_inertial_params = self.api7_module.IMassInertiaParam7(
e
)
area = mass_inertial_params.Area * 0.0001 # м²
material = getattr(e, 'Material', 'Неизвестно')
material = getattr(e, "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:
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"
for w in welding:
if welding_key not in sorted_stats:
sorted_stats[welding_key] = {}
w_name_split = w.Name.split('-')
w_len = w_name_split[-1].split('@')[0]
w_name_split = w.Name.split("-")
w_len = w_name_split[-1].split("@")[0]
sorted_stats[welding_key][w.Name] = w_len
if welding_key in sorted_stats:
def float_f(n):
try:
return float(n)
except:
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
for section in sorted_stats:
print(section)
for name, value in sorted_stats[section].items():
print(f"{name} -- {value}")
print('-----')
print("-----")
except Exception as e:
print(f"[ERROR] Ошибка при обработке документа #{i + 1}: {e}")