├── parse_design_documents.py └── README.md /parse_design_documents.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import subprocess 4 | import pythoncom 5 | from win32com.client import Dispatch, gencache 6 | from tkinter import Tk 7 | from tkinter.filedialog import askopenfilenames 8 | 9 | 10 | # Подключение к API7 программы Компас 3D 11 | def get_kompas_api7(): 12 | module = gencache.EnsureModule("{69AC2981-37C0-4379-84FD-5DD2F3C0A520}", 0, 1, 0) 13 | api = module.IKompasAPIObject( 14 | Dispatch("Kompas.Application.7")._oleobj_.QueryInterface(module.IKompasAPIObject.CLSID, 15 | pythoncom.IID_IDispatch)) 16 | const = gencache.EnsureModule("{75C9F5D0-B5B8-4526-8681-9903C567D2ED}", 0, 1, 0).constants 17 | return module, api, const 18 | 19 | 20 | # Функция проверки, запущена-ли программа КОМПАС 3D 21 | def is_running(): 22 | proc_list = \ 23 | subprocess.Popen('tasklist /NH /FI "IMAGENAME eq KOMPAS*"', shell=False, stdout=subprocess.PIPE).communicate()[0] 24 | return True if proc_list else False 25 | 26 | 27 | # Посчитаем количество листов каждого из формата 28 | def amount_sheet(doc7): 29 | sheets = {"A0": 0, "A1": 0, "A2": 0, "A3": 0, "A4": 0, "A5": 0} 30 | for sheet in range(doc7.LayoutSheets.Count): 31 | format = doc7.LayoutSheets.Item(sheet).Format # sheet - номер листа, отсчёт начинается от 0 32 | sheets["A" + str(format.Format)] += 1 * format.FormatMultiplicity 33 | return sheets 34 | 35 | 36 | # Прочитаем основную надпись чертежа 37 | def stamp(doc7): 38 | for sheet in range(doc7.LayoutSheets.Count): 39 | style_filename = os.path.basename(doc7.LayoutSheets.Item(sheet).LayoutLibraryFileName) 40 | style_number = int(doc7.LayoutSheets.Item(sheet).LayoutStyleNumber) 41 | 42 | if style_filename in ['graphic.lyt', 'Graphic.lyt'] and style_number == 1: 43 | stamp = doc7.LayoutSheets.Item(sheet).Stamp 44 | return {"Scale": re.findall(r"\d+:\d+", stamp.Text(6).Str)[0], 45 | "Designer": stamp.Text(110).Str} 46 | 47 | return 'Неопределенный стиль оформления' 48 | 49 | 50 | # Подсчет технических требований, в том случае, если включена автоматическая нумерация 51 | def count_demand(doc7, module7): 52 | IDrawingDocument = doc7._oleobj_.QueryInterface(module7.NamesToIIDMap['IDrawingDocument'], pythoncom.IID_IDispatch) 53 | drawing_doc = module7.IDrawingDocument(IDrawingDocument) 54 | text_demand = drawing_doc.TechnicalDemand.Text 55 | 56 | count = 0 # Количество пунктов технических требований 57 | for i in range(text_demand.Count): # Прохоим по каждой строчке технических требований 58 | if text_demand.TextLines[i].Numbering == 1: # и проверяем, есть ли у строки нумерация 59 | count += 1 60 | 61 | # Если нет нумерации, но есть текст 62 | if not count and text_demand.TextLines[0]: 63 | count += 1 64 | 65 | return count 66 | 67 | 68 | # Подсчёт размеров на чертеже, для каждого вида по отдельности 69 | def count_dimension(doc7, module7): 70 | IKompasDocument2D = doc7._oleobj_.QueryInterface(module7.NamesToIIDMap['IKompasDocument2D'], 71 | pythoncom.IID_IDispatch) 72 | doc2D = module7.IKompasDocument2D(IKompasDocument2D) 73 | views = doc2D.ViewsAndLayersManager.Views 74 | 75 | count_dim = 0 76 | for i in range(views.Count): 77 | ISymbols2DContainer = views.View(i)._oleobj_.QueryInterface(module7.NamesToIIDMap['ISymbols2DContainer'], 78 | pythoncom.IID_IDispatch) 79 | dimensions = module7.ISymbols2DContainer(ISymbols2DContainer) 80 | 81 | # Складываем все необходимые раpмеры 82 | count_dim += dimensions.AngleDimensions.Count + \ 83 | dimensions.ArcDimensions.Count + \ 84 | dimensions.Bases.Count + \ 85 | dimensions.BreakLineDimensions.Count + \ 86 | dimensions.BreakRadialDimensions.Count + \ 87 | dimensions.DiametralDimensions.Count + \ 88 | dimensions.Leaders.Count + \ 89 | dimensions.LineDimensions.Count + \ 90 | dimensions.RadialDimensions.Count + \ 91 | dimensions.RemoteElements.Count + \ 92 | dimensions.Roughs.Count + \ 93 | dimensions.Tolerances.Count 94 | 95 | return count_dim 96 | 97 | 98 | def parse_design_documents(paths): 99 | is_run = is_running() # True, если программа Компас уже запущена 100 | 101 | module7, api7, const7 = get_kompas_api7() # Подключаемся к программе 102 | app7 = api7.Application # Получаем основной интерфейс программы 103 | app7.Visible = True # Показываем окно пользователю (если скрыто) 104 | app7.HideMessage = const7.ksHideMessageNo # Отвечаем НЕТ на любые вопросы программы 105 | 106 | table = [] # Создаём таблицу парметров 107 | for path in paths: 108 | doc7 = app7.Documents.Open(PathName=path, 109 | Visible=True, 110 | ReadOnly=True) # Откроем файл в видимом режиме без права его изменять 111 | 112 | row = amount_sheet(doc7) # Посчитаем кол-во листов каждого формат 113 | row.update(stamp(doc7)) # Читаем основную надпись 114 | row.update({ 115 | "Filename": doc7.Name, # Имя файла 116 | "CountTD": count_demand(doc7, module7), # Количество пунктов технических требований 117 | "CountDim": count_dimension(doc7, module7), # Количество пунктов технических требований 118 | }) 119 | table.append(row) # Добавляем строку параметров в таблицу 120 | 121 | doc7.Close(const7.kdDoNotSaveChanges) # Закроем файл без изменения 122 | 123 | if not is_run: app7.Quit() # Закрываем программу при необходимости 124 | return table 125 | 126 | 127 | def print_to_excel(result): 128 | excel = Dispatch("Excel.Application") # Подключаемся к программе Excel 129 | excel.Visible = True # Делаем окно видимым 130 | wb = excel.Workbooks.Add() # Добавляем новую книгу 131 | sheet = wb.ActiveSheet # Получаем ссылку на активный лист 132 | 133 | # Создаём заголовок таблицы 134 | sheet.Range("A1:J1").value = ["Имя файла", "Разработчик", "Кол-во размеров", "Кол-во пунктов ТТ", 135 | "А0", "А1", "А2", "А3", "А4", "Масштаб"] 136 | 137 | # Заполняем таблицу 138 | for i, row in enumerate(result): 139 | sheet.Cells(i + 2, 1).value = row['Filename'] 140 | sheet.Cells(i + 2, 2).value = row['Designer'] 141 | sheet.Cells(i + 2, 3).value = row['CountDim'] 142 | sheet.Cells(i + 2, 4).value = row['CountTD'] 143 | sheet.Cells(i + 2, 5).value = row['A0'] 144 | sheet.Cells(i + 2, 6).value = row['A1'] 145 | sheet.Cells(i + 2, 7).value = row['A2'] 146 | sheet.Cells(i + 2, 8).value = row['A3'] 147 | sheet.Cells(i + 2, 9).value = row['A4'] 148 | sheet.Cells(i + 2, 10).value = "".join(('="', row['Scale'], '"')) 149 | 150 | 151 | if __name__ == "__main__": 152 | root = Tk() 153 | root.withdraw() # Скрываем основное окно и сразу окно выбора файлов 154 | 155 | filenames = askopenfilenames(title="Выберети чертежи деталей", filetypes=[('Компас 3D', '*.cdw'), ]) 156 | 157 | print_to_excel(parse_design_documents(filenames)) 158 | 159 | root.destroy() # Уничтожаем основное окно 160 | root.mainloop() 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python на службе у конструктора. Укрощаем API Kompas 3D 2 | ![Logo](https://habrastorage.org/files/75a/869/fe6/75a869fe65854087a5b965b1b2c82f75.png "Python конструктора") 3 | 4 | Работая в конструкторском отделе, я столкнулся с задачей - рассчитать трудоёмкость разработки конструкторской документации. Если брать за основу документ: [«Типовые нормативы времени на разработку конструкторской документации. ШИФР 13.01.01" (утв. Минтрудом России 07.03.2014 N 003)»](http://www.consultant.ru/document/cons_doc_LAW_199653/), то для расчета трудоёмкости чертежа детали нам необходимы следующие данные: 5 | 6 | * Формат чертежа и количество листов 7 | * Масштаб 8 | * Количество размеров на чертеже (включая знаки шероховатости и выносные линии) 9 | * Количество технических требований 10 | 11 | Из имеющихся инструментов на предприятии имеем: Kompas 3D v14 и Python 3.5. 12 | 13 | В интернете не так много статей о написании программ с использованием API Kompas 3D, и ещё меньше информации о том, как это сделать на Python. Попробую рассказать по шагам, как решалась поставленная задача и на какие грабли приходилось наступать. Статья рассчитана на людей, владеющих основами программирования и знакомых с языком Python. Итак, приступим. 14 | 15 | ## Подготовительная операция: 16 | Убедитесь, что на вашем компьютере установлена программа Kompas 3D, версии не ниже 14 и Python 3. Вам также необходимо установить [pywin3](http://sourceforge.net/projects/pywin32/) (Python for Windows extensions). 17 | 18 | ## Подключение к Kompas 3D: 19 | Система Kompas 3D имеет две версии API: API5, которая предоставляет интерфейс KompasObject, и API7, предоставляющая интерфейс IKompasAPIObject. API версии 5 и 7 во многом дублируют свой функционал, но, со слов разработчиков, в 7-ой версии более выражен объектно-ориентированный подход. В данной статье акцент сделан на 7-ю версию. 20 | 21 | Функция подключения выглядит следующим образом: 22 | 23 | ``` 24 | import pythoncom 25 | from win32com.client import Dispatch, gencache 26 | 27 | # Подключение к API7 программы Kompas 3D 28 | def get_kompas_api7(): 29 | module = gencache.EnsureModule("{69AC2981-37C0-4379-84FD-5DD2F3C0A520}", 0, 1, 0) 30 | api = module.IKompasAPIObject( 31 | Dispatch("Kompas.Application.7")._oleobj_.QueryInterface(module.IKompasAPIObject.CLSID, 32 | pythoncom.IID_IDispatch)) 33 | const = gencache.EnsureModule("{75C9F5D0-B5B8-4526-8681-9903C567D2ED}", 0, 1, 0).constants 34 | return module, api, const 35 | ``` 36 | Чуть подробнее о модуле win32com [здесь](http://docs.activestate.com/activepython/3.4/pywin32/html/com/win32com/HTML/GeneratedSupport.html). 37 | 38 | Теперь, чтобы подключиться к интерфейсу, нам понадобиться следующий код: 39 | 40 | ``` 41 | module7, api7, const7 = get_kompas_api7() # Подключаемся к API7 42 | app7 = api7.Application # Получаем основной интерфейс 43 | app7.Visible = True # Показываем окно пользователю (если скрыто) 44 | app7.HideMessage = const7.ksHideMessageNo # Отвечаем НЕТ на любые вопросы программы 45 | print(app7.ApplicationName(FullName=True)) # Печатаем название программы 46 | ``` 47 | Для более глубокого понимания API заглянем в SDK. На моём компьютере она находится по адресу: C:\Program Files\ASCON\KOMPAS-3D V16\SDK\SDK.chm. Здесь можно подробнее узнать, например, о методе HideMessage: 48 | 49 | ![HideMessage](https://habrastorage.org/files/d4f/094/eb2/d4f094eb276a474b92ae63235b35e46a.png "Окно справки по SDK") 50 | 51 | После выполнения нашего кода вернём всё на свои места: если Kompas 3D был запущен нами (в процессе работы скрипта), мы его и закроем. Самый простой способ определить, запущен ли процесс, - использовать стандартный модуль subprocess: 52 | 53 | ``` 54 | import subprocess 55 | 56 | # Функция проверяет, запущена ли программа Kompas 3D 57 | def is_running(): 58 | proc_list = subprocess.Popen('tasklist /NH /FI "IMAGENAME eq KOMPAS*"', 59 | shell=False, 60 | stdout=subprocess.PIPE).communicate()[0] 61 | return True if proc_list else False 62 | ``` 63 | 64 | Данная функция проверяет, запущен ли процесс «KOMPAS» [стандартными методами Windows](https://technet.microsoft.com/en-us/library/bb491010.aspx#mainSection). Обратите внимание, что разные версии программы Kompas 3D могут иметь разные наименования процессов! 65 | 66 | ## Считаем количество листов и их формат: 67 | Тут всё просто: у нашего документа doc7 имеется интерфейс коллекции листов оформления LayoutSheets. Каждый лист обладает свойством формата и кратности. Для Компаса, начиная с 15 версии, интерфейс LayoutSheets доступен не только для файлов чертежей, но и для спецификаций и текстовых документов. 68 | 69 | ``` 70 | # Посчитаем количество листов каждого из формата 71 | def amount_sheet(doc7): 72 | sheets = {"A0": 0, "A1": 0, "A2": 0, "A3": 0, "A4": 0, "A5": 0} 73 | for sheet in range(doc7.LayoutSheets.Count): 74 | format = doc7.LayoutSheets.Item(sheet).Format # sheet - номер листа, отсчёт начинается от 0 75 | sheets["A" + str(format.Format)] += 1 * format.FormatMultiplicity 76 | return sheets 77 | ``` 78 | 79 | Посмотрим на процесс изучения SDK для поиска интересующих нас функций: 80 | 81 | ![Animated](https://habrastorage.org/files/d71/883/e2c/d71883e2c3654b0fb7ca0bec92b8998c.gif) 82 | 83 | 84 | ## Читаем основную надпись: 85 | Здесь нам поможет всё тот же LayoutSheets: 86 | 87 | ``` 88 | # Прочитаем масштаб из штампа, ячейка №6 89 | def stamp_scale(doc7): 90 | stamp = doc7.LayoutSheets.Item(0).Stamp # Item(0) указывает на штамп первого листа 91 | return stamp.Text(6).Str 92 | ``` 93 | 94 | На самом деле ячейка №6 для листа с другим оформлением может содержать не масштаб, а совсем иную информацию. Посмотрим, как в Kompas 3D определяются стили оформления чертежа: 95 | 96 | ![Style](https://habrastorage.org/files/371/b6d/490/371b6d490efb4ce59876457318c4513d.png) 97 | 98 | Таким образом, важно проверять, какому файлу и номеру оформления соответствует лист чертежа. Также стоит помнить, что документ может содержать титульный лист! Поэтому придётся усложнить код. Применим [регулярные выражения](https://habrahabr.ru/post/115825/), т.к. текст в ячейке может являться ссылкой: 99 | 100 | ``` 101 | import os 102 | import re 103 | 104 | # Прочитаем основную надпись чертежа 105 | def stamp(doc7): 106 | for sheet in range(doc7.LayoutSheets.Count): 107 | style_filename = os.path.basename(doc7.LayoutSheets.Item(sheet).LayoutLibraryFileName) 108 | style_number = int(doc7.LayoutSheets.Item(sheet).LayoutStyleNumber) 109 | 110 | if style_filename in ['graphic.lyt', 'Graphic.lyt'] and style_number == 1: 111 | stamp = doc7.LayoutSheets.Item(sheet).Stamp 112 | return {"Scale": re.search(r"\d+:\d+", stamp.Text(6).Str).group(), 113 | "Designer": stamp.Text(110).Str} 114 | 115 | return {"Scale": 'Неопределенный стиль оформления', 116 | "Designer": 'Неопределенный стиль оформления'} 117 | ``` 118 | 119 | Остался последний вопрос: как узнать нужный номер ячейки? Для этих целей удобно создать файл чертежа, в котором интересующие нас ячейки будут заполнены, а после - прочитать все возможные варианты с помощью следующей функции: 120 | 121 | ``` 122 | # Просмотр всех ячеек 123 | def parse_stamp(doc7, number_sheet): 124 | stamp = doc7.LayoutSheets.Item(number_sheet).Stamp 125 | for i in range(10000): 126 | if stamp.Text(i).Str: 127 | print('Номер ячейки = %-5d Значение = %s' % (i, stamp.Text(i).Str)) 128 | ``` 129 | 130 | ## Считаем количество пунктов технических требований: 131 | Согласно SDK, нам всего-то нужно получить интерфейс TechnicalDemand от IDrawingDocument, а 132 | IDrawingDocument можно получить от iDocuments с помощью замечательного метода с говорящим названием IUnknown::QueryInterface. И только в SDK 16 версии Kompas 3D появилось разъяснение, как это сделать: 133 | 134 | ![IUnknown](https://habrastorage.org/files/e7c/a58/b7e/e7ca58b7efdb49f9ace1f39ad64b7f12.png) 135 | 136 | С такими разъяснениями легко написать следующее: 137 | 138 | ``` 139 | # Подсчет технических требований, в том случае, если включена автоматическая нумерация 140 | def count_TT(doc7, module7): 141 | doc2D_s = doc7._oleobj_.QueryInterface(module7.NamesToIIDMap['IDrawingDocument'], 142 | pythoncom.IID_IDispatch) 143 | doc2D = module7.IDrawingDocument(doc2D_s) 144 | text_TT = doc2D.TechnicalDemand.Text 145 | 146 | count_tt = 0 # Количество пунктов технических требований 147 | for i in range(text_TT.Count): # Проходим по каждой строчке технических требований 148 | if text_TT.TextLines[i].Numbering == 1: # и проверяем, есть ли у строки нумерация 149 | count_tt += 1 150 | 151 | # Если нет нумерации, но есть текст 152 | if not count_tt and text_TT.TextLines[0]: 153 | count_tt += 1 154 | 155 | return count_tt 156 | ``` 157 | 158 | Стоит отметить, что данный код полагается на автоматическую нумерацию технических требований. Так что, если автоматическая нумерация не применялась или технические требования набраны с использованием простого инструмента «Текст», код будет сложнее. Оставляю решение данной задачи на читателя. 159 | 160 | ## Считаем количество размеров на чертеже: 161 | При подсчёте размеров, надо иметь в виду, что необходимо посчитать их на каждом из видов чертежа: 162 | 163 | ``` 164 | # Подсчёт размеров на чертеже, для каждого вида по отдельности 165 | def count_dimension(doc7, module7): 166 | IKompasDocument2D = doc7._oleobj_.QueryInterface(module7.NamesToIIDMap['IKompasDocument2D'], 167 | pythoncom.IID_IDispatch) 168 | doc2D = module7.IKompasDocument2D(IKompasDocument2D) 169 | views = doc2D.ViewsAndLayersManager.Views 170 | 171 | count_dim = 0 172 | for i in range(views.Count): 173 | ISymbols2DContainer = views.View(i)._oleobj_.QueryInterface(module7.NamesToIIDMap['ISymbols2DContainer'], 174 | pythoncom.IID_IDispatch) 175 | dimensions = module7.ISymbols2DContainer(ISymbols2DContainer) 176 | 177 | # Складываем все необходимые размеры 178 | count_dim += dimensions.AngleDimensions.Count + \ 179 | dimensions.ArcDimensions.Count + \ 180 | dimensions.Bases.Count + \ 181 | dimensions.BreakLineDimensions.Count + \ 182 | dimensions.BreakRadialDimensions.Count + \ 183 | dimensions.DiametralDimensions.Count + \ 184 | dimensions.Leaders.Count + \ 185 | dimensions.LineDimensions.Count + \ 186 | dimensions.RadialDimensions.Count + \ 187 | dimensions.RemoteElements.Count + \ 188 | dimensions.Roughs.Count + \ 189 | dimensions.Tolerances.Count 190 | 191 | return count_dim 192 | ``` 193 | 194 | ## Основная функция скрипта 195 | В результате проделанной работы мы получили следующее: 196 | 197 | ``` 198 | def parse_design_documents(paths): 199 | is_run = is_running() # Установим флаг, который нам говорит, 200 | # запущена ли программа до запуска нашего скрипта 201 | 202 | module7, api7, const7 = get_kompas_api7() # Подключаемся к программе 203 | app7 = api7.Application # Получаем основной интерфейс программы 204 | app7.Visible = True # Показываем окно пользователю (если скрыто) 205 | app7.HideMessage = const7.ksHideMessageNo # Отвечаем НЕТ на любые вопросы программы 206 | 207 | table = [] # Создаём таблицу параметров 208 | for path in paths: 209 | doc7 = app7.Documents.Open(PathName=path, 210 | Visible=True, 211 | ReadOnly=True) # Откроем файл в видимом режиме без права его изменять 212 | 213 | row = amount_sheet(doc7) # Посчитаем кол-во листов каждого формат 214 | row.update(stamp(doc7)) # Читаем основную надпись 215 | row.update({ 216 | "Filename": doc7.Name, # Имя файла 217 | "CountTD": count_demand(doc7, module7), # Количество пунктов технических требований 218 | "CountDim": count_dimension(doc7, module7), # Количество размеров на чертеже 219 | }) 220 | table.append(row) # Добавляем строку параметров в таблицу 221 | 222 | doc7.Close(const7.kdDoNotSaveChanges) # Закроем файл без изменения 223 | 224 | if not is_run: app7.Quit() # Выходим из программы 225 | return table 226 | ``` 227 | 228 | ## Диалоговое окно выбора файлов 229 | Для удобного использования нашего скрипта воспользуемся возможностями стандартного модуля tkinter и выведем диалоговое окно выбора файлов: 230 | 231 | ``` 232 | from tkinter import Tk 233 | from tkinter.filedialog import askopenfilenames 234 | 235 | if __name__ == "__main__": 236 | root = Tk() 237 | root.withdraw() # Скрываем основное окно и сразу окно выбора файлов 238 | 239 | filenames = askopenfilenames(title="Выберете чертежи деталей", 240 | filetypes=[('Kompas 3D', '*.cdw'),]) 241 | 242 | print_to_excel(parse_design_documents(filenames)) 243 | 244 | root.destroy() # Уничтожаем основное окно 245 | root.mainloop() 246 | ``` 247 | 248 | ## Отчётность 249 | Чтобы не рисовать в tkintere интерфейс пользователя, предлагаю воспользоваться хорошей программой Excel, куда и выведем результат нашего труда: 250 | 251 | ``` 252 | def print_to_excel(result): 253 | excel = Dispatch("Excel.Application") # Подключаемся к Excel 254 | excel.Visible = True # Делаем окно видимым 255 | wb = excel.Workbooks.Add() # Добавляем новую книгу 256 | sheet = wb.ActiveSheet # Получаем ссылку на активный лист 257 | 258 | # Создаём заголовок таблицы 259 | sheet.Range("A1:J1").value = ["Имя файла", "Разработчик", 260 | "Кол-во размеров", "Кол-во пунктов ТТ", 261 | "А0", "А1", "А2", "А3", "А4", "Масштаб"] 262 | 263 | # Заполняем таблицу 264 | for i, row in enumerate(result): 265 | sheet.Range("A%d:J%d" % i+2).value = [row['Filename'], 266 | row['Designer'], 267 | row['CountDim'], 268 | row['CountTD'], 269 | row['A0'], 270 | row['A1'], 271 | row['A2'], 272 | row['A3'], 273 | row['A4'], 274 | "".join(('="', row['Scale'], '"'))] 275 | ``` 276 | 277 | Если не полениться и правильно подготовить Excel файл, то результат работы нашего скрипта можно сразу представить в наглядном виде: 278 | 279 | ![Здесь красивая цветная картинка из Excel](https://habrastorage.org/files/368/ebd/e0b/368ebde0bc76418b91e57ab9e5a1e7b6.png) 280 | 281 | На графике изображено участие каждого сотрудника отдела в выпуске документации на изделие. 282 | 283 | ## Заключение 284 | Используя скрипт для чертежа, созданного специально для данной статьи, мы получим следующие результаты: 285 | * Количество размеров: 25 штук 286 | * Количество пуктов технических требований: 3 штуки 287 | * Масштаб: 1:1 288 | * Количетво листов: 1, формата А3 289 | 290 | Трудозатраты, согласно упомянатому в начале статьи документу, составили: 1 час 20 минут. Как ни странно, примерно столько и было потрачено времени на разработку чертежа. 291 | Конечно, для внедрения подобных норм на предприятии нужны более серьёзные исследования и совершенно другие объёмы конструкторской документации. Данная же статья поможет упростить работу с API Kompas 3D при решении аналогичных задач. 292 | Буду рад любым вашим замечаниями и предложениями к статье. 293 | 294 | ## Исходный код 295 | * [GitHub](https://github.com/Weltraum/Article-Python-in-aid-of-the-designer.-Tames-API-Kompas-3D.) 296 | 297 | ## Ресурсы в помощь: 298 | 1. SDK (C:\Program Files\ASCON\KOMPAS-3D V16\SDK\SDK.chm) 299 | 2. [forum.ascon.ru](forum.ascon.ru) 300 | --------------------------------------------------------------------------------