client/parser/main.py

559 lines
24 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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}",
"api_7": "{69AC2981-37C0-4379-84FD-5DD2F3C0A520}",
"const": "{75C9F5D0-B5B8-4526-8681-9903C567D2ED}",
}
class KompasDocumentParser:
def __init__(self):
self.api5 = None
self.api7 = None
self.constants = None
self.application = None
self._init_kompas()
def _init_kompas(self):
"""Инициализация API КОМПАС версий 5 и 7"""
try:
import pythoncom
pythoncom.CoInitialize()
# Получаем API версии 5
self.api5_module = gencache.EnsureModule(ids["api_5"], 0, 1, 0)
self.api5 = self.api5_module.KompasObject(
Dispatch("Kompas.Application.5")._oleobj_.QueryInterface(
self.api5_module.KompasObject.CLSID, pythoncom.IID_IDispatch
)
)
# Получаем API версии 7
self.api7_module = gencache.EnsureModule(ids["api_7"], 0, 1, 0)
self.api7 = self.api7_module.IKompasAPIObject(
Dispatch("Kompas.Application.7")._oleobj_.QueryInterface(
self.api7_module.IKompasAPIObject.CLSID, pythoncom.IID_IDispatch
)
)
# Получаем константы
constants_module = gencache.EnsureModule(ids["const"], 0, 1, 0)
self.constants = constants_module.constants
# Правильное получение IApplication
self.application = self.api7_module.IApplication(self.api7)
self.application.Visible = True
except Exception as e:
# Выводим полный traceback и сообщение об ошибке
print("[ERROR] Ошибка при подключении к КОМПАС:")
traceback.print_exc() # <-- Выводит номер строки, файл и стек
raise RuntimeError(f"Ошибка при подключении к КОМПАС: {e}")
def get_open_documents(self):
"""Возвращает список информации о всех открытых документах"""
documents = []
docs_collection = self.application.Documents
for i in range(docs_collection.Count):
try:
doc = docs_collection.Item(i) # Индексация с 1
if doc is None:
continue
documents.append(
{
"type": doc.DocumentType,
"name": doc.Name,
"path": doc.Path,
"active": doc.Active,
}
)
except Exception as e:
print(f"Ошибка при обработке документа #{i + 1}: {e}")
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,
},
}