922 lines
38 KiB
Python
922 lines
38 KiB
Python
import os
|
||
import traceback
|
||
import time
|
||
import re
|
||
from collections import defaultdict
|
||
from win32com.client import Dispatch, gencache
|
||
from PIL import Image, ImageDraw, ImageFont
|
||
import xmltodict
|
||
import pythoncom
|
||
from win32com.client import Dispatch, VARIANT
|
||
|
||
from enums import KompasCommand
|
||
from logger import logger
|
||
|
||
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_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):
|
||
"""Возвращает список информации о всех открытых документах"""
|
||
|
||
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):
|
||
"""
|
||
Создание чертежей для всех уникальных деталей из открытых деталей и сборок.
|
||
Также создаёт спецификацию для сборок.
|
||
"""
|
||
logger.info("Начинаем создание чертежей для деталей...")
|
||
result = {"result": []}
|
||
# Получаем доступные типы из разрешённых действий
|
||
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_type = doc.DocumentType
|
||
if doc_type not in allowed_types:
|
||
continue
|
||
|
||
doc.Active = True
|
||
doc_path = doc.Path
|
||
doc_name = "-".join(doc.Name.split(".")[:-1])
|
||
logger.info(f"Обрабатываем документ: {doc_name}")
|
||
|
||
doc_3d = self.api7_module.IKompasDocument3D(doc)
|
||
top_part = doc_3d.TopPart
|
||
|
||
# Используем общую рекурсивную функцию вместо all_elements
|
||
if doc_type == self.constants.ksDocumentAssembly:
|
||
result_recursive = self._traverse_parts_recursive(top_part)
|
||
elements = result_recursive["elements"]
|
||
welding = result_recursive["welding"]
|
||
bends = result_recursive["bends"]
|
||
else:
|
||
# Для отдельной детали просто собираем данные
|
||
elements = [top_part]
|
||
welding = self._collect_welds_from_drawing(top_part)
|
||
bends = self._collect_bends_from_element(top_part)
|
||
|
||
logger.info(f"Найдено элементов для создания чертежей: {len(elements)}")
|
||
|
||
# Сохранение чертежей
|
||
drawings = []
|
||
for component in elements:
|
||
component_ipart = self.api7_module.IPart7(component)
|
||
if component_ipart.Standard:
|
||
continue
|
||
|
||
logger.info(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]
|
||
if hasattr(component_ipart.Owner.ResultBodies, "GetGabarit"):
|
||
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))]
|
||
|
||
# Масштабирование
|
||
c_scale = (
|
||
c_sheet.Format.FormatHeight / sum(c_size)
|
||
if sum(c_size) > 0
|
||
else 1
|
||
)
|
||
|
||
# Получаем интерфейс 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 = self.prepare_export_path(doc_path, filename, "cdw")
|
||
c_doc.SaveAs(full_path)
|
||
logger.info(f"[OK] Сохранён чертёж: {full_path}")
|
||
|
||
drawings.append(
|
||
{
|
||
"name": f"{filename}.cdw",
|
||
"path": full_path,
|
||
"success": True,
|
||
"timestamp": time.time(),
|
||
}
|
||
)
|
||
|
||
# Сохранение спецификации для сборок
|
||
specifications = []
|
||
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 = self.prepare_export_path(doc_path, filename, "spw")
|
||
spec.SaveAs(full_path)
|
||
logger.info(f"[OK] Сохранена спецификация: {full_path}")
|
||
|
||
specifications.append(
|
||
{
|
||
"name": f"{filename}.spw",
|
||
"path": full_path,
|
||
"success": True,
|
||
"timestamp": time.time(),
|
||
}
|
||
)
|
||
|
||
# Добавляем результат
|
||
result["result"].append(
|
||
{
|
||
"document_name": doc_name,
|
||
"drawings": drawings,
|
||
"specifications": specifications,
|
||
"success": len(drawings) > 0 or len(specifications) > 0,
|
||
"timestamp": time.time(),
|
||
}
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(
|
||
f"[ERROR] Ошибка при обработке документа #{i + 1}: {e}",
|
||
exc_info=True,
|
||
)
|
||
result["result"].append(
|
||
{
|
||
"document_name": f"Документ #{i + 1}",
|
||
"drawings": [],
|
||
"specifications": [],
|
||
"success": False,
|
||
"error": str(e),
|
||
"timestamp": time.time(),
|
||
}
|
||
)
|
||
|
||
return result
|
||
|
||
def save_to_iges(self):
|
||
"""Сохраняет открытые 3D-документы (детали/сборки) в формате 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:
|
||
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 = "-".join(doc.Name.split(".")[:-1])
|
||
logger.info(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
|
||
|
||
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 export_success:
|
||
logger.error(f"Не удалось сохранить: {full_path}")
|
||
|
||
except Exception as e:
|
||
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_bends_from_element(self, element):
|
||
bends = []
|
||
try:
|
||
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)
|
||
except Exception as e:
|
||
logger.error("Ошибка при сборе гибов из элемента", exc_info=True)
|
||
return bends
|
||
|
||
def _collect_welds_from_drawing(self, part):
|
||
welding = []
|
||
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:
|
||
logger.error(
|
||
"Ошибка при сборе сварок из чертежного контекста", exc_info=True
|
||
)
|
||
return welding
|
||
|
||
def _traverse_parts_recursive(self, part):
|
||
elements = []
|
||
welding = []
|
||
bends = []
|
||
|
||
try:
|
||
# Собираем сварки
|
||
welding += self._collect_welds_from_drawing(part)
|
||
|
||
# Обходим подчасти
|
||
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)
|
||
elements.append(element)
|
||
bends += self._collect_bends_from_element(element)
|
||
result = self._traverse_parts_recursive(element)
|
||
elements += result["elements"]
|
||
welding += result["welding"]
|
||
bends += result["bends"]
|
||
|
||
except Exception as e:
|
||
logger.error("Ошибка при рекурсивном обходе частей", exc_info=True)
|
||
|
||
return {"elements": elements, "welding": welding, "bends": bends}
|
||
|
||
def _build_statistics_data(self, elements, welding):
|
||
stats = {"Name": {}, "Material": {}, "Area": {}}
|
||
item_template = lambda: {"quantity": 0, "area": 0, "mass": 0, "material": ""}
|
||
detail_stats = {
|
||
"standard": defaultdict(item_template),
|
||
"custom": defaultdict(item_template),
|
||
}
|
||
# Сбор данных по элементам (кроме "Welding")
|
||
for e in elements:
|
||
type_key = "standard" if e.Standard else "custom"
|
||
|
||
name = getattr(e, "Name", "Неизвестно")
|
||
material = getattr(e, "Material", "Неизвестно")
|
||
|
||
element_key = (name, material)
|
||
|
||
mass_inertial_params = self.api7_module.IMassInertiaParam7(e)
|
||
area = mass_inertial_params.Area * 0.0001
|
||
mass = round(getattr(e, "Mass"), 3)
|
||
quantity = 1
|
||
|
||
detail_stats[type_key][element_key]["material"] = material
|
||
detail_stats[type_key][element_key]["quantity"] += quantity
|
||
detail_stats[type_key][element_key]["area"] += area
|
||
detail_stats[type_key][element_key]["mass"] += mass
|
||
|
||
for key in stats.keys():
|
||
if key == "Name":
|
||
value = f"{getattr(e, key)}, масса {round(getattr(e, 'Mass'), 3)}"
|
||
stats[key][value] = 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}, м²:"
|
||
stats[key][key_area] = round(stats[key].get(key_area, 0) + area, 6)
|
||
else:
|
||
try:
|
||
value = getattr(e, key)
|
||
stats[key][value] = stats[key].get(value, 0) + 1
|
||
except AttributeError:
|
||
logger.warning(f"Элемент не имеет атрибута '{key}', пропускаем")
|
||
# logger.info(detail_stats)
|
||
# Общая площадь
|
||
stats["Area"]["Total"] = sum(stats["Area"].values())
|
||
|
||
# Добавляем сварки в статистику как отдельный раздел
|
||
stats["Welding"] = {}
|
||
|
||
for w in welding:
|
||
w_name_split = w.Name.split("-")
|
||
w_len = w_name_split[-1].split("@")[0]
|
||
stats["Welding"][w.Name] = w_len
|
||
|
||
if stats["Welding"]:
|
||
total_length = sum(
|
||
float(w_len)
|
||
for w_len in stats["Welding"].values()
|
||
if w_len.replace(".", "", 1).isdigit()
|
||
)
|
||
stats["Welding"]["Total"] = round(total_length, 2)
|
||
|
||
return stats
|
||
|
||
def extract_section(name):
|
||
"""Извлечение сечения из имени профиля"""
|
||
match = re.search(r"(\d+)х(\d+)(?:х(\d+))?", name)
|
||
if match:
|
||
w, h, t = match.groups()
|
||
return f"{w}x{h}" + (f", толщ. {t}" if t else "")
|
||
return "Без сечения"
|
||
|
||
def classify_item(self, name, obj):
|
||
"""Классификация элементов по типу и имени"""
|
||
name_lower = name.lower()
|
||
cls_name = str(type(obj))
|
||
|
||
# Гиб листовой детали
|
||
if isinstance(
|
||
obj,
|
||
(self.api7_module.ISheetMetalBend, self.api7_module.ISheetMetalLineBend),
|
||
):
|
||
return "Гиб"
|
||
|
||
# Сварной шов
|
||
elif "IUserDesignationCompObj" in cls_name or (
|
||
"IUserObject3D" in cls_name and "свар" in name_lower
|
||
):
|
||
return "Сварной шов"
|
||
|
||
# Стандартное изделие
|
||
elif "IMacroObject3D" in cls_name or any(
|
||
kw in name_lower for kw in ["болт", "гайка", "шайба"]
|
||
):
|
||
return "Стандартное изделие"
|
||
|
||
# Профильная труба
|
||
elif "IUserObject3D" in cls_name and any(
|
||
kw in name_lower for kw in ["труба", "профиль"]
|
||
):
|
||
return "Профильная труба"
|
||
|
||
# Листовая оболочка
|
||
elif "ISheetMetalRuledShell" in cls_name:
|
||
return "Листовая деталь"
|
||
|
||
# Обычная деталь
|
||
elif "IPart7" in cls_name:
|
||
return "Кастомная деталь"
|
||
|
||
# Вспомогательные объекты
|
||
elif any(
|
||
kw in cls_name
|
||
for kw in [
|
||
"IPlane3D",
|
||
"IAxis3D",
|
||
"IModelObject",
|
||
"IMateConstraint3D",
|
||
"ISketch",
|
||
]
|
||
):
|
||
return "Вспомогательный объект"
|
||
|
||
else:
|
||
return "Неизвестный тип"
|
||
|
||
def merge_results(self, target, source):
|
||
for category in ["Standard", "Sheet", "Pipe"]:
|
||
for key, items in source.get(category, {}).items():
|
||
target[category].setdefault(key, []).extend(items)
|
||
return target
|
||
|
||
def keeper(self, obj):
|
||
property_keeper = self.api7_module.IPropertyKeeper(obj)
|
||
try:
|
||
data = xmltodict.parse(property_keeper.Properties)
|
||
properties_list = data.get("infObject", {}).get("property", [])
|
||
if not isinstance(properties_list, list):
|
||
properties_list = [properties_list]
|
||
|
||
# Создаём словарь по @id
|
||
props = {prop["@id"]: prop for prop in properties_list if "@id" in prop}
|
||
if props.get("276039607982", None):
|
||
section_prop = props.get("276039607982") # Сечение (тип профиля)
|
||
sketch_prop = props.get("316618764777") # Эскиз сечения
|
||
profile_length_prop = props.get("235833998283") # Длина профиля
|
||
key = sketch_prop["@value"]
|
||
return (key, profile_length_prop)
|
||
except Exception as e:
|
||
logger.error(e)
|
||
return None
|
||
|
||
return None
|
||
|
||
def traverse_ipart(self, element, depth=0, viewed=None, result=None):
|
||
if viewed is None:
|
||
viewed = set()
|
||
|
||
obj_id = id(element)
|
||
if obj_id in viewed:
|
||
return
|
||
viewed.add(obj_id)
|
||
|
||
if result is None:
|
||
result = {"Standard": {}, "Sheet": {}, "Pipe": {}, "Weld": {}}
|
||
|
||
property_manager = self.api7_module.IPropertyMng(self.application)
|
||
|
||
parts_collection = self.api7_module.IPart7(element).Parts
|
||
if not isinstance(parts_collection, tuple):
|
||
parts_collection = (parts_collection,)
|
||
|
||
sub_features = self.api7_module.IFeature7(element).SubFeatures(1, True, True)
|
||
if not isinstance(sub_features, tuple):
|
||
sub_features = (sub_features,)
|
||
|
||
bodies = self.api7_module.IFeature7(element).ResultBodies
|
||
if not isinstance(bodies, tuple):
|
||
bodies = (bodies,)
|
||
|
||
for f in parts_collection + sub_features + bodies:
|
||
print(f"Обрабатываем: {type(f)} - {getattr(f, 'Name', 'Без имени')}")
|
||
if getattr(f, "Standard", False):
|
||
key = (f.Name, getattr(f, "Material", "Без материала"))
|
||
result["Standard"].setdefault(key, []).append(f.Name)
|
||
|
||
elif getattr(f, "FileName", None):
|
||
param = f.GetOpenDocumentParam()
|
||
param.Visible = True
|
||
param.ReadOnly = True
|
||
new_doc3d = f.OpenSourceDocument(param)
|
||
new_doc = self.api7_module.IKompasDocument(new_doc3d)
|
||
new_doc.Active = True
|
||
new_doc3d = self.api7_module.IKompasDocument3D(new_doc)
|
||
new_result = self.traverse_ipart(new_doc3d.TopPart)
|
||
|
||
self.merge_results(result, new_result)
|
||
new_doc.Close(self.constants.kdDoNotSaveChanges)
|
||
|
||
else:
|
||
if isinstance(f, self.api7_module.IBody7):
|
||
res = self.keeper(f)
|
||
if res:
|
||
key, value = res
|
||
result["Pipe"].setdefault(key, []).append(value)
|
||
|
||
if isinstance(f, self.api7_module.IUserDesignationCompObj):
|
||
weldes = self.api7_module.IFeature7(f).SubFeatures(1, True, True)
|
||
print(f.Name)
|
||
for w in weldes:
|
||
# Получаем IUserParameters
|
||
user_params = self.api7_module.IUserParameters(w)
|
||
|
||
# Получаем имя файла библиотеки
|
||
lib_filename = user_params.LibraryFileName # Например: Welding3D.rtw
|
||
print("Библиотека:", lib_filename)
|
||
|
||
# Создаем VARIANT с путём к библиотеке (VT_BSTR)
|
||
var_libname = VARIANT(pythoncom.VT_BSTR, lib_filename)
|
||
property_manager = self.api7_module.IPropertyMng(w.Application)
|
||
properties_variant = property_manager.GetProperties(var_libname)
|
||
|
||
print(properties_variant)
|
||
raw_data = bytes(self.api7_module.IUserParameters(w).UserParams)
|
||
decoded = raw_data.strip(b'\x00').decode('utf-16le', errors='ignore')
|
||
# Разделяем на строки (если есть разделители)
|
||
parts = decoded.split('\x00') # Разделение по нулевым символам
|
||
print(decoded)
|
||
print(raw_data)
|
||
for ww in range(w.AssociationObjectCount):
|
||
_ww = w.AssociationObject(ww)
|
||
if _ww:
|
||
print(_ww.Name)
|
||
# if _ww:
|
||
# if _ww.ModelObjectType == 7:
|
||
# print(property_manager.GetReport(_ww.Application.ActiveDocument))
|
||
|
||
if isinstance(f, self.api7_module.ISheetMetalRuledShell):
|
||
key = getattr(f, "Name", "----")
|
||
value = getattr(f, "Name", "----")
|
||
result["Sheet"].setdefault(key, []).append(value)
|
||
|
||
if isinstance(
|
||
f,
|
||
(
|
||
self.api7_module.IPart7,
|
||
self.api7_module.IBody7,
|
||
self.api7_module.IFeature7,
|
||
),
|
||
):
|
||
self.traverse_ipart(f, depth + 1, viewed, result)
|
||
|
||
return result
|
||
|
||
def collect_statistics(self):
|
||
"""Сбор статистики по элементам, гибам и сваркам в активном документе"""
|
||
logger.info("Начинаем сбор статистики по элементам...")
|
||
|
||
av_actions = self.get_available_actions()
|
||
allowed_types = av_actions[KompasCommand.STATS]["allowed_types"]
|
||
|
||
docs_collection = self.application.Documents
|
||
result = {"result": []}
|
||
|
||
for i in range(docs_collection.Count + 1):
|
||
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])
|
||
logger.info(f"Обрабатываем документ: {doc_name}")
|
||
|
||
elements = []
|
||
bends = []
|
||
welding = []
|
||
|
||
doc_3d = self.api7_module.IKompasDocument3D(doc)
|
||
top_part = doc_3d.TopPart
|
||
|
||
if doc_type == self.constants.ksDocumentAssembly:
|
||
# Рекурсивный обход для сборки
|
||
res = self._traverse_parts_recursive(top_part)
|
||
|
||
elements += res["elements"]
|
||
bends += res["bends"]
|
||
welding += res["welding"]
|
||
else:
|
||
# Для деталей просто добавляем верхнюю часть и собираем данные
|
||
elements += [top_part]
|
||
bends += self._collect_bends_from_element(top_part)
|
||
welding += self._collect_welds_from_drawing(top_part)
|
||
|
||
res = self.traverse_ipart(top_part)
|
||
print("@@@")
|
||
print(res)
|
||
logger.info(
|
||
f"Найдено:\n Элементов {len(elements)}\n Гибов {len(bends)}\n"
|
||
)
|
||
|
||
stats = self._build_statistics_data(elements, welding)
|
||
# Формируем объект документа для результата
|
||
result["result"].append(
|
||
{
|
||
"name": doc_name,
|
||
"elements_count": len(elements),
|
||
"bends_count": len(bends),
|
||
"statistics": stats,
|
||
}
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"[ERROR] Ошибка при обработке документа #{i + 1}: {e}")
|
||
traceback.print_exc()
|
||
|
||
return result
|
||
|
||
def export_to_raster(self):
|
||
"""Экспорт открытых 2D-документов (чертежи, фрагменты, спецификации) в JPG и DXF. Создание многостраничного PDF."""
|
||
logger.info("Начинаем экспорт документов в растровый формат...")
|
||
images = []
|
||
first_doc_name = None
|
||
result = {"result": []}
|
||
|
||
# Получаем доступные типы для экспорта из разрешенных действий
|
||
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] # Имя без расширения
|
||
logger.info(f"Обрабатываем документ: {doc_name}")
|
||
|
||
# Сохраняем имя первого документа
|
||
if first_doc_name is None:
|
||
try:
|
||
doc7 = self.api7_module.IKompasDocument(doc)
|
||
first_doc_name = (
|
||
doc7.LayoutSheets.ItemByNumber(1).Stamp.Text(2).Str
|
||
)
|
||
except Exception as e:
|
||
first_doc_name = "Без имени"
|
||
logger.warning("Не удалось получить имя документа из штампа")
|
||
|
||
# Получаем интерфейс 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
|
||
|
||
paths = {}
|
||
# Сохранение в JPG и DXF
|
||
for ext in ["jpg", "dxf"]:
|
||
full_path = self.prepare_export_path(doc_path, doc_name, ext)
|
||
if ext == "jpg":
|
||
doc_api5.SaveAsToRasterFormat(full_path, raster_params)
|
||
logger.info(f"[OK] Сохранен JPG: {full_path}")
|
||
try:
|
||
img = Image.open(full_path)
|
||
images.append(img.copy())
|
||
img.close()
|
||
except Exception as e:
|
||
logger.error(
|
||
f"Не удалось открыть изображение: {full_path}",
|
||
exc_info=True,
|
||
)
|
||
elif ext == "dxf":
|
||
doc_api5.ksSaveToDXF(full_path)
|
||
logger.info(f"Сохранен DXF: {full_path}")
|
||
|
||
paths[ext] = full_path
|
||
# Добавляем результат в список
|
||
result["result"].append(
|
||
{
|
||
"document_name": doc_name,
|
||
"document_type": doc_type,
|
||
"paths": paths,
|
||
"success": True,
|
||
"timestamp": time.time(),
|
||
}
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"[ERROR] Не удалось создать PDF: {e}", exc_info=True)
|
||
result["result"].append(
|
||
{
|
||
"file": None,
|
||
"success": False,
|
||
"error": str(e),
|
||
"document_index": i + 1,
|
||
"timestamp": time.time(),
|
||
}
|
||
)
|
||
|
||
# Создание 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,
|
||
)
|
||
logger.info(f"[OK] PDF успешно сохранен: {pdf_output_path}")
|
||
result["result"].append(
|
||
{
|
||
"document_name": pdf_output_path,
|
||
"document_type": "pdf",
|
||
"paths": pdf_output_path,
|
||
"success": True,
|
||
"timestamp": time.time(),
|
||
}
|
||
)
|
||
except Exception as e:
|
||
logger.info(f"[ERROR] Не удалось создать PDF: {e}")
|
||
|
||
return result
|
||
|
||
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,
|
||
},
|
||
}
|