Compare commits

...

2 Commits

Author SHA1 Message Date
ksenia_mikhailova 9230dbced0 add all last func 2025-06-24 13:41:38 +03:00
ksenia_mikhailova 27ea6e903e actions list 2025-06-24 12:53:36 +03:00
5 changed files with 501 additions and 39 deletions

View File

@ -33,8 +33,17 @@ class PythonJSBridge(QObject):
data = None
if command == KompasCommand.OPEN_KOMPAS:
data = self.kompas.get_open_documents()
return json.dumps(data)
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)
def send_event_to_js(self, event_type: str, data: dict):
self.emitter.onEvent.emit(event_type, data)

View File

@ -2,6 +2,12 @@ from enum import Enum
class KompasCommand(str, Enum):
OPEN_KOMPAS = "open_kompas"
GET_AVAILABLE_ACTIONS = "get_available_actions"
IGES = "iges"
STATS = "stats"
EXPORT_RASTER = "export_raster"
PROJECT_SUPPORT = "project_support"
@classmethod
def has_value(cls, value):

View File

@ -7,23 +7,17 @@ from PyQt6.QtCore import QUrl, QThread
from config import MAIN_URL
from bridge import PythonJSBridge
from parser.main import KompasDocumentParser
class KompasWorker(QThread):
def run(self):
pythoncom.CoInitialize()
self.parser = KompasDocumentParser()
class BrowserWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QT6 Браузер")
self.resize(1000, 800)
self.kompas_thread = KompasWorker()
self.kompas_thread.start()
self.resize(1366, 768)
container = QWidget()
layout = QVBoxLayout()

View File

@ -2,6 +2,9 @@ import os
import traceback
import pythoncom
from win32com.client import Dispatch, gencache
from PIL import Image, ImageDraw, ImageFont
from enums import KompasCommand
ids = {
"api_5": "{0422828C-F174-495E-AC5D-D31014DBBE87}",
@ -22,6 +25,7 @@ class KompasDocumentParser:
"""Инициализация API КОМПАС версий 5 и 7"""
try:
import pythoncom
pythoncom.CoInitialize()
# Получаем API версии 5
@ -62,7 +66,9 @@ class KompasDocumentParser:
for i in range(docs_collection.Count):
try:
doc = docs_collection.Item(i + 1) # Индексация с 1
doc = docs_collection.Item(i) # Индексация с 1
if doc is None:
continue
documents.append(
{
@ -75,4 +81,478 @@ class KompasDocumentParser:
except Exception as e:
print(f"Ошибка при обработке документа #{i + 1}: {e}")
return documents
return documents
def create_drawing_for_parts(self):
"""
Создание чертежей для всех уникальных деталей из открытых деталей и сборок.
Также создаёт спецификацию для сборок.
"""
print("Начинаем создание чертежей для деталей...")
saving_path = "../cdw/"
# Получаем доступные типы из разрешённых действий
av_actions = self.get_available_actions()
allowed_types = av_actions[KompasCommand.PROJECT_SUPPORT]["allowed_types"]
docs_collection = self.application.Documents
for i in range(docs_collection.Count):
try:
doc = docs_collection.Item(i)
if doc is None:
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
doc.Active = True
doc_name = "-".join(doc.Name.split(".")[:-1])
print(f"Обрабатываем документ: {doc_name}")
# Сохранение чертежа
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_3d = self.api7_module.IKompasDocument3D(doc)
doc_components = []
def all_elements(part):
"""Рекурсивный обход всех компонентов сборки"""
parts = self.api7_module.IParts7(part.Parts)
for j in range(parts.Count):
element = parts.Part(j)
if element.Parts.Count == 0:
if not element.Standard and not any(
el.Name == element.Name for el in doc_components
):
doc_components.append(element)
else:
all_elements(element)
if doc_type == self.constants.ksDocumentAssembly:
all_elements(doc_3d.TopPart)
else:
doc_components.append(doc_3d.TopPart)
for component in doc_components:
component_ipart = self.api7_module.IPart7(component)
if component_ipart.Standard:
continue
print(f"Создаём чертёж для: {component.Name}")
# Создаём новый чертёж
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
c_sheet.Format.VerticalOrientation = False
c_sheet.Update()
# Расчёт габаритов
c_size = [1, 1, 1]
if component_ipart.Owner.ResultBodies:
gabarit = [0, 0, 0, 0, 0, 0]
component_ipart.Owner.ResultBodies.GetGabarit(*gabarit)
g1 = gabarit[1:4]
g2 = gabarit[4:]
c_size = [abs(g1[i] - g2[i]) for i in range(len(g1))]
g = max(c_size)
# Масштабирование
c_scale = c_sheet.Format.FormatHeight / sum(c_size)
# Получаем интерфейс 2D документа
c_doc_2d = self.api7_module.IKompasDocument2D(c_doc)
c_views = c_doc_2d.ViewsAndLayersManager.Views
# Добавляем стандартные виды
c_views.AddStandartViews(
component_ipart.FileName,
component_ipart.Name,
[1, 3, 5, 7],
c_size[1] * c_scale,
c_sheet.Format.FormatHeight - 25,
c_scale,
20,
20,
)
filename = "_".join(
filter(
None,
[component_ipart.Marking, component_ipart.Name[:20]],
)
).replace(" ", "-")
full_path = os.path.join(output_dir, f"{filename}.cdw")
c_doc.SaveAs(full_path)
print(f"[OK] Сохранён чертёж: {full_path}")
# Сохранение спецификации для сборок
if doc_type == self.constants.ksDocumentAssembly:
spec = self.application.Documents.Add(
self.constants.ksDocumentSpecification
)
spec_doc = self.api7_module.ISpecificationDocument(spec)
spec_doc.AttachedDocuments.Add(doc.PathName, True)
filename = "Список_деталей"
full_path = os.path.join(output_dir, f"{filename}.spw")
spec.SaveAs(full_path)
print(f"[OK] Сохранена спецификация: {full_path}")
except Exception as e:
print(f"[ERROR] Ошибка при обработке документа #{i + 1}: {e}")
traceback.print_exc()
def save_to_iges(self):
"""Сохраняет открытые 3D-документы (детали/сборки) в формате IGES"""
print("Начинаем сохранение документов в формате IGES...")
av_actions = self.get_available_actions()
docs_collection = self.application.Documents
for i in range(docs_collection.Count):
try:
doc = docs_collection.Item(i)
if doc is None:
continue
doc_type = doc.DocumentType
if doc_type not in av_actions[KompasCommand.IGES]["allowed_types"]:
continue
doc.Active = True
doc_path = doc.Path
doc_name = "-".join(doc.Name.split(".")[:-1])
print(f"Попытка сохранить: {doc_name}")
# Получаем 3D-документ через API v5
doc_api5 = self.api5.ActiveDocument3D()
if not doc_api5:
raise RuntimeError(
"Не удалось получить активный 3D-документ через API v5"
)
# Подготавливаем параметры сохранения в IGES
save_params = doc_api5.AdditionFormatParam()
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)
# Создаём директорию, если её нет
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}")
except Exception as e:
print(f"[ERROR] Ошибка при обработке документа #{i + 1}: {e}")
traceback.print_exc()
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):
try:
doc = docs_collection.Item(i)
if doc is None:
continue
doc_type = doc.DocumentType
if doc_type not in allowed_types:
continue
doc.Active = True
doc_name = "-".join(doc.Name.split(".")[:-1])
print(f"Обрабатываем документ: {doc_name}")
doc_3d = self.api7_module.IKompasDocument3D(doc)
top_part = doc_3d.TopPart
elements = []
bends = []
welding = []
def look_features(element):
feature = self.api7_module.IFeature7(element)
sub_features = feature.SubFeatures(1, True, False) or []
for item in sub_features:
if type(item) in (
self.api7_module.ISheetMetalBend,
self.api7_module.ISheetMetalLineBend,
self.api7_module.ISheetMetalBody,
):
sub_sheets = item.Owner.SubFeatures(1, True, False)
if sub_sheets:
for b in sub_sheets:
bend = self.api7_module.ISheetMetalBend(b)
bends.append(bend)
def look_drawing(part):
drawing_context = self.api7_module.IDrawingContainer(part)
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):
welding.append(item)
def find_el(part):
try:
drawing_context = self.api7_module.IDrawingContainer(part)
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):
welding.append(item)
except Exception as e:
print('Ошибка в DrawingContext:', e)
try:
doc_parts = self.api7_module.IParts7(part.Parts)
for j in range(doc_parts.Count):
element = doc_parts.Part(j)
if element.Parts.Count == 0:
elements.append(element)
look_features(element)
find_el(element)
except Exception as e:
print('Ошибка в Parts:', e)
if doc_type == self.constants.ksDocumentAssembly:
find_el(top_part)
else:
elements.append(top_part)
look_drawing(top_part)
look_features(top_part)
print(f"Найдено:\n Элементов {len(elements)}\n Гибов {len(bends)}\n")
sorted_stats = {
"Name": {},
"Material": {},
"Area": {},
}
for e in elements:
for key in sorted_stats.keys():
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)
area = mass_inertial_params.Area * 0.0001 # м²
material = getattr(e, 'Material', 'Неизвестно')
key_area = f"Площадь {material}, м²:"
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['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]
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())
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('-----')
except Exception as e:
print(f"[ERROR] Ошибка при обработке документа #{i + 1}: {e}")
traceback.print_exc()
def export_to_raster(self):
"""Экспорт открытых 2D-документов (чертежи, фрагменты, спецификации) в JPG и DXF. Создание многостраничного PDF."""
print("Начинаем экспорт документов в растровый формат...")
images = []
first_doc_name = None
# Получаем доступные типы для экспорта из разрешенных действий
av_actions = self.get_available_actions()
allowed_types = av_actions[KompasCommand.EXPORT_RASTER]["allowed_types"]
docs_collection = self.application.Documents
for i in range(docs_collection.Count):
try:
doc = docs_collection.Item(i)
if doc is None:
continue
doc_type = doc.DocumentType
if doc_type not in allowed_types:
continue
doc.Active = True
doc_path = doc.Path
doc_name = os.path.splitext(doc.Name)[0] # Имя без расширения
print(f"Обрабатываем документ: {doc_name}")
# Сохраняем имя первого документа
if first_doc_name is None:
doc7 = self.api7_module.IKompasDocument(doc)
first_doc_name = doc7.LayoutSheets.ItemByNumber(1).Stamp.Text(2).Str
# Получаем интерфейс 2D-документа
if doc_type == self.constants.ksDocumentSpecification:
doc_api5 = self.api5.SpcActiveDocument()
else:
doc_api5 = self.api5.ActiveDocument2D()
if not doc_api5:
raise RuntimeError("Не удалось получить активный 2D-документ")
# Параметры экспорта в JPG
raster_params = doc_api5.RasterFormatParam()
raster_params.Init()
raster_params.colorBPP = 8
raster_params.colorType = 3
raster_params.extResolution = 96
raster_params.format = 0 # JPEG format
# Сохранение в JPG и DXF
for ext in ["jpg", "dxf"]:
output_dir = os.path.join(doc_path, ext)
filename = f"{doc_name}.{ext}"
full_path = os.path.join(output_dir, filename)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
print(f"Создана папка: {output_dir}")
if ext == "jpg":
doc_api5.SaveAsToRasterFormat(full_path, raster_params)
print(f"[OK] Сохранен JPG: {full_path}")
images.append(Image.open(full_path))
elif ext == "dxf":
doc_api5.ksSaveToDXF(full_path)
print(f"[OK] Сохранен DXF: {full_path}")
except Exception as e:
print(f"[ERROR] Ошибка при обработке документа #{i + 1}: {e}")
traceback.print_exc()
# Создание PDF
if images:
desktop_path = os.path.expanduser("~\\Desktop")
pdf_filename = f"{first_doc_name}_pages.pdf"
pdf_output_path = os.path.join(desktop_path, pdf_filename)
# Создаем титульную страницу с названием и количеством страниц
try:
font = ImageFont.truetype("arial.ttf", size=48)
except IOError:
print("Шрифт Arial не найден. Используется стандартный шрифт.")
font = ImageFont.load_default()
title_image = Image.new("RGB", (images[0].width, 200), color="white")
draw = ImageDraw.Draw(title_image)
title_text = f"{first_doc_name}\nКоличество страниц: {len(images)}"
draw.text((10, 50), title_text, fill="black", font=font, spacing=10)
images.insert(0, title_image)
# Сохраняем как PDF
try:
images[0].save(
pdf_output_path,
save_all=True,
append_images=images[1:],
format="PDF",
resolution=96.0,
)
print(f"[OK] PDF успешно сохранен: {pdf_output_path}")
except Exception as e:
print(f"[ERROR] Не удалось создать PDF: {e}")
def get_available_actions(self):
"""
Возвращает список доступных действий и допустимые типы документов
Формат: {
action_key: {
'label': str,
'allowed_types': list,
'method': str,
'command': str
}
}
"""
# Допустимые типы документов
ALLOWED_TYPES_3D = [
self.constants.ksDocumentPart,
self.constants.ksDocumentAssembly,
]
ALLOWED_TYPES_2D = [
self.constants.ksDocumentDrawing,
self.constants.ksDocumentFragment,
]
ALLOWED_TYPES_SPEC = [self.constants.ksDocumentSpecification]
ALLOWED_TYPES_ALL = ALLOWED_TYPES_3D + ALLOWED_TYPES_2D + ALLOWED_TYPES_SPEC
return {
KompasCommand.IGES: {
"label": "Сохранить как IGES",
"allowed_types": ALLOWED_TYPES_3D,
"method": "save_to_iges",
"command": KompasCommand.IGES,
},
KompasCommand.STATS: {
"label": "Собрать статистику по элементам",
"allowed_types": ALLOWED_TYPES_3D,
"method": "collect_statistics",
"command": KompasCommand.STATS,
},
KompasCommand.EXPORT_RASTER: {
"label": "Экспортировать в PDF",
"allowed_types": ALLOWED_TYPES_2D + ALLOWED_TYPES_SPEC,
"method": "export_to_raster",
"command": KompasCommand.EXPORT_RASTER,
},
KompasCommand.PROJECT_SUPPORT: {
"label": "Создать чертежи деталей",
"allowed_types": ALLOWED_TYPES_3D,
"method": "create_drawing_for_parts",
"command": KompasCommand.PROJECT_SUPPORT,
},
}

View File

@ -1,27 +0,0 @@
# kompas_worker.py
from PyQt6.QtCore import QObject, pyqtSignal
import pythoncom
from parser.main import KompasDocumentParser
class KompasWorker(QObject):
result_ready = pyqtSignal(object)
error_occurred = pyqtSignal(str)
def run_kompas(self):
try:
# Инициализируем COM правильно
pythoncom.CoInitializeEx(pythoncom.COINIT_APARTMENTTHREADED)
# Выполняем работу с КОМПАС
parser = KompasDocumentParser()
documents = parser.get_open_documents()
# Отправляем результат обратно
self.result_ready.emit(documents)
except Exception as e:
import traceback
error_msg = f"[Ошибка] {e}\n{traceback.format_exc()}"
self.error_occurred.emit(error_msg)
finally:
pythoncom.CoUninitialize()