[Из песочницы] Python на службе у конструктора. Укрощаем API Kompas 3D
Работая в конструкторском отделе, я столкнулся с задачей — рассчитать трудоёмкость разработки конструкторской документации. Если брать за основу документ: «Типовые нормативы времени на разработку конструкторской документации. ШИФР 13.01.01» (утв. Минтрудом России 07.03.2014 N 003)», то для расчета трудоёмкости чертежа детали нам необходимы следующие данные:
- Формат чертежа и количество листов
- Масштаб
- Количество размеров на чертеже (включая знаки шероховатости и выносные линии)
- Количество технических требований
Из имеющихся инструментов на предприятии имеем: Kompas 3D v14 и Python 3.5.
В интернете не так много статей о написании программ с использованием API Kompas 3D, и ещё меньше информации о том, как это сделать на Python. Попробую рассказать по шагам, как решалась поставленная задача и на какие грабли приходилось наступать. Статья рассчитана на людей, владеющих основами программирования и знакомых с языком Python. Итак, приступим.
Подготовительная операция:
Убедитесь, что на вашем компьютере установлена программа Kompas 3D, версии не ниже 14 и Python 3. Вам также необходимо установить pywin3 (Python for Windows extensions).
Подключение к Kompas 3D:
Система Kompas 3D имеет две версии API: API5, которая предоставляет интерфейс KompasObject, и API7, предоставляющая интерфейс IKompasAPIObject. API версии 5 и 7 во многом дублируют свой функционал, но, со слов разработчиков, в 7-ой версии более выражен объектно-ориентированный подход. В данной статье акцент сделан на 7-ю версию.
Функция подключения выглядит следующим образом:
import pythoncom
from win32com.client import Dispatch, gencache
# Подключение к API7 программы Kompas 3D
def get_kompas_api7():
module = gencache.EnsureModule("{69AC2981-37C0-4379-84FD-5DD2F3C0A520}", 0, 1, 0)
api = module.IKompasAPIObject(
Dispatch("Kompas.Application.7")._oleobj_.QueryInterface(module.IKompasAPIObject.CLSID,
pythoncom.IID_IDispatch))
const = gencache.EnsureModule("{75C9F5D0-B5B8-4526-8681-9903C567D2ED}", 0, 1, 0).constants
return module, api, const
Чуть подробнее о модуле win32com здесь.
Теперь, чтобы подключиться к интерфейсу, нам понадобиться следующий код:
module7, api7, const7 = get_kompas_api7() # Подключаемся к API7
app7 = api7.Application # Получаем основной интерфейс
app7.Visible = True # Показываем окно пользователю (если скрыто)
app7.HideMessage = const7.ksHideMessageNo # Отвечаем НЕТ на любые вопросы программы
print(app7.ApplicationName(FullName=True)) # Печатаем название программы
Для более глубокого понимания API заглянем в SDK. На моём компьютере она находится по адресу: C:\Program Files\ASCON\KOMPAS-3D V16\SDK\SDK.chm. Здесь можно подробнее узнать, например, о методе HideMessage:
После выполнения нашего кода вернём всё на свои места: если Kompas 3D был запущен нами (в процессе работы скрипта), мы его и закроем. Самый простой способ определить, запущен ли процесс, — использовать стандартный модуль subprocess:
import subprocess
# Функция проверяет, запущена ли программа Kompas 3D
def is_running():
proc_list = subprocess.Popen('tasklist /NH /FI "IMAGENAME eq KOMPAS*"',
shell=False,
stdout=subprocess.PIPE).communicate()[0]
return True if proc_list else False
Данная функция проверяет, запущен ли процесс «KOMPAS» стандартными методами Windows. Обратите внимание, что разные версии программы Kompas 3D могут иметь разные наименования процессов!
Считаем количество листов и их формат:
Тут всё просто: у нашего документа doc7 имеется интерфейс коллекции листов оформления LayoutSheets. Каждый лист обладает свойством формата и кратности. Для Компаса, начиная с 15 версии, интерфейс LayoutSheets доступен не только для файлов чертежей, но и для спецификаций и текстовых документов.
# Посчитаем количество листов каждого из формата
def amount_sheet(doc7):
sheets = {"A0": 0, "A1": 0, "A2": 0, "A3": 0, "A4": 0, "A5": 0}
for sheet in range(doc7.LayoutSheets.Count):
format = doc7.LayoutSheets.Item(sheet).Format # sheet - номер листа, отсчёт начинается от 0
sheets["A" + str(format.Format)] += 1 * format.FormatMultiplicity
return sheets
Посмотрим на процесс изучения SDK для поиска интересующих нас функций:
Читаем основную надпись:
Здесь нам поможет всё тот же LayoutSheets:
# Прочитаем масштаб из штампа, ячейка №6
def stamp_scale(doc7):
stamp = doc7.LayoutSheets.Item(0).Stamp # Item(0) указывает на штамп первого листа
return stamp.Text(6).Str
На самом деле ячейка №6 для листа с другим оформлением может содержать не масштаб, а совсем иную информацию. Посмотрим, как в Kompas 3D определяются стили оформления чертежа:
Таким образом, важно проверять, какому файлу и номеру оформления соответствует лист чертежа. Также стоит помнить, что документ может содержать титульный лист! Поэтому придётся усложнить код. Применим регулярные выражения, т.к. текст в ячейке может являться ссылкой:
import os
import re
# Прочитаем основную надпись чертежа
def stamp(doc7):
for sheet in range(doc7.LayoutSheets.Count):
style_filename = os.path.basename(doc7.LayoutSheets.Item(sheet).LayoutLibraryFileName)
style_number = int(doc7.LayoutSheets.Item(sheet).LayoutStyleNumber)
if style_filename in ['graphic.lyt', 'Graphic.lyt'] and style_number == 1:
stamp = doc7.LayoutSheets.Item(sheet).Stamp
return {"Scale": re.search(r"\d+:\d+", stamp.Text(6).Str).group(),
"Designer": stamp.Text(110).Str}
return {"Scale": 'Неопределенный стиль оформления',
"Designer": 'Неопределенный стиль оформления'}
Остался последний вопрос: как узнать нужный номер ячейки? Для этих целей удобно создать файл чертежа, в котором интересующие нас ячейки будут заполнены, а после — прочитать все возможные варианты с помощью следующей функции:
# Просмотр всех ячеек
def parse_stamp(doc7, number_sheet):
stamp = doc7.LayoutSheets.Item(number_sheet).Stamp
for i in range(10000):
if stamp.Text(i).Str:
print('Номер ячейки = %-5d Значение = %s' % (i, stamp.Text(i).Str))
Считаем количество пунктов технических требований:
Согласно SDK, нам всего-то нужно получить интерфейс TechnicalDemand от IDrawingDocument, а
IDrawingDocument можно получить от iDocuments с помощью замечательного метода с говорящим названием IUnknown: QueryInterface. И только в SDK 16 версии Kompas 3D появилось разъяснение, как это сделать:
С такими разъяснениями легко написать следующее:
# Подсчет технических требований, в том случае, если включена автоматическая нумерация
def count_TT(doc7, module7):
doc2D_s = doc7._oleobj_.QueryInterface(module7.NamesToIIDMap['IDrawingDocument'],
pythoncom.IID_IDispatch)
doc2D = module7.IDrawingDocument(doc2D_s)
text_TT = doc2D.TechnicalDemand.Text
count_tt = 0 # Количество пунктов технических требований
for i in range(text_TT.Count): # Проходим по каждой строчке технических требований
if text_TT.TextLines[i].Numbering == 1: # и проверяем, есть ли у строки нумерация
count_tt += 1
# Если нет нумерации, но есть текст
if not count_tt and text_TT.TextLines[0]:
count_tt += 1
return count_tt
Стоит отметить, что данный код полагается на автоматическую нумерацию технических требований. Так что, если автоматическая нумерация не применялась или технические требования набраны с использованием простого инструмента «Текст», код будет сложнее. Оставляю решение данной задачи на читателя.
Считаем количество размеров на чертеже:
При подсчёте размеров, надо иметь в виду, что необходимо посчитать их на каждом из видов чертежа:
# Подсчёт размеров на чертеже, для каждого вида по отдельности
def count_dimension(doc7, module7):
IKompasDocument2D = doc7._oleobj_.QueryInterface(module7.NamesToIIDMap['IKompasDocument2D'],
pythoncom.IID_IDispatch)
doc2D = module7.IKompasDocument2D(IKompasDocument2D)
views = doc2D.ViewsAndLayersManager.Views
count_dim = 0
for i in range(views.Count):
ISymbols2DContainer = views.View(i)._oleobj_.QueryInterface(module7.NamesToIIDMap['ISymbols2DContainer'],
pythoncom.IID_IDispatch)
dimensions = module7.ISymbols2DContainer(ISymbols2DContainer)
# Складываем все необходимые размеры
count_dim += dimensions.AngleDimensions.Count + \
dimensions.ArcDimensions.Count + \
dimensions.Bases.Count + \
dimensions.BreakLineDimensions.Count + \
dimensions.BreakRadialDimensions.Count + \
dimensions.DiametralDimensions.Count + \
dimensions.Leaders.Count + \
dimensions.LineDimensions.Count + \
dimensions.RadialDimensions.Count + \
dimensions.RemoteElements.Count + \
dimensions.Roughs.Count + \
dimensions.Tolerances.Count
return count_dim
Основная функция скрипта
В результате проделанной работы мы получили следующее:
def parse_design_documents(paths):
is_run = is_running() # Установим флаг, который нам говорит,
# запущена ли программа до запуска нашего скрипта
module7, api7, const7 = get_kompas_api7() # Подключаемся к программе
app7 = api7.Application # Получаем основной интерфейс программы
app7.Visible = True # Показываем окно пользователю (если скрыто)
app7.HideMessage = const7.ksHideMessageNo # Отвечаем НЕТ на любые вопросы программы
table = [] # Создаём таблицу параметров
for path in paths:
doc7 = app7.Documents.Open(PathName=path,
Visible=True,
ReadOnly=True) # Откроем файл в видимом режиме без права его изменять
row = amount_sheet(doc7) # Посчитаем кол-во листов каждого формат
row.update(stamp(doc7)) # Читаем основную надпись
row.update({
"Filename": doc7.Name, # Имя файла
"CountTD": count_demand(doc7, module7), # Количество пунктов технических требований
"CountDim": count_dimension(doc7, module7), # Количество размеров на чертеже
})
table.append(row) # Добавляем строку параметров в таблицу
doc7.Close(const7.kdDoNotSaveChanges) # Закроем файл без изменения
if not is_run: app7.Quit() # Выходим из программы
return table
Диалоговое окно выбора файлов
Для удобного использования нашего скрипта воспользуемся возможностями стандартного модуля tkinter и выведем диалоговое окно выбора файлов:
from tkinter import Tk
from tkinter.filedialog import askopenfilenames
if __name__ == "__main__":
root = Tk()
root.withdraw() # Скрываем основное окно и сразу окно выбора файлов
filenames = askopenfilenames(title="Выберете чертежи деталей",
filetypes=[('Kompas 3D', '*.cdw'),])
print_to_excel(parse_design_documents(filenames))
root.destroy() # Уничтожаем основное окно
root.mainloop()
Отчётность
Чтобы не рисовать в tkintere интерфейс пользователя, предлагаю воспользоваться хорошей программой Excel, куда и выведем результат нашего труда:
def print_to_excel(result):
excel = Dispatch("Excel.Application") # Подключаемся к Excel
excel.Visible = True # Делаем окно видимым
wb = excel.Workbooks.Add() # Добавляем новую книгу
sheet = wb.ActiveSheet # Получаем ссылку на активный лист
# Создаём заголовок таблицы
sheet.Range("A1:J1").value = ["Имя файла", "Разработчик",
"Кол-во размеров", "Кол-во пунктов ТТ",
"А0", "А1", "А2", "А3", "А4", "Масштаб"]
# Заполняем таблицу
for i, row in enumerate(result):
sheet.Range("A%d:J%d" % i+2).value = [row['Filename'],
row['Designer'],
row['CountDim'],
row['CountTD'],
row['A0'],
row['A1'],
row['A2'],
row['A3'],
row['A4'],
"".join(('="', row['Scale'], '"'))]
Если не полениться и правильно подготовить Excel файл, то результат работы нашего скрипта можно сразу представить в наглядном виде:
На графике изображено участие каждого сотрудника отдела в выпуске документации на изделие.
Заключение
Используя скрипт для чертежа, созданного специально для данной статьи, мы получим следующие результаты:
- Количество размеров: 25 штук
- Количество пунктов технических требований: 3 штуки
- Масштаб: 1:1
- Количество листов: 1, формата А3
Трудозатраты, согласно упомянутому в начале статьи документу, составили: 1 час 20 минут. Как ни странно, примерно столько и было потрачено времени на разработку чертежа.
Конечно, для внедрения подобных норм на предприятии нужны более серьёзные исследования и совершенно другие объёмы конструкторской документации. Данная же статья поможет упростить работу с API Kompas 3D при решении аналогичных задач.
Буду рад любым вашим замечаниями и предложениями к статье.
Исходный код
- GitHub
Ресурсы в помощь:
- SDK (C:\Program Files\ASCON\KOMPAS-3D V16\SDK\SDK.chm)
- forum.ascon.ru
Комментарии (1)
2 марта 2017 в 14:01
0↑
↓
А в питоне, конечно, трудоемкость следует считать интегрированием отступов начальных. Какая там трудоемкость вашего питоньего решения выходит?