Python & ActiveX/COM Autocad

Автор Тема: Python & ActiveX/COM Autocad  (Прочитано 230330 раз)

0 Пользователей и 7 Гостей просматривают эту тему.

Оффлайн Derie1

  • ADN OPEN
  • Сообщений: 13
  • Карма: 0
Re: Python & ActiveX/COM Autocad
« Ответ #495 : 07-04-2022, 22:10:21 »
Странно, тестирую этот код на домашнем компьютере, работает без сбоев. Причем все операции делаются заметно быстрее. Уже раз 50 запускал, ни одного сбоя нет.
На работе 2021 AutoCAD, дома 2015.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Python & ActiveX/COM Autocad
« Ответ #496 : 07-04-2022, 22:19:49 »
Derie1,
Оба AutoCAD x64? Вообще очень много зависит от версии Windows, версии AutoCAD, дополнительно установленных программ и обновлений Windows/AutoCAD, права (администратор/пользователь) в Windows.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Derie1

  • ADN OPEN
  • Сообщений: 13
  • Карма: 0
Re: Python & ActiveX/COM Autocad
« Ответ #497 : 07-04-2022, 22:43:11 »
Александр Ривилис,
да, обе версии x64. Ну да, дома права администратора, да и процессор в домашнем компе побыстрее, если это влияет, конечно. Но отрабатывает скрипт значительно быстрее на домашнем. Видно на глаз даже. Ладно, завтра буду пробовать задержку ставить при ошибке.
Я так понимаю, цикл надо примерно так переписать?
Код - Python [Выбрать]
  1. for obj in objSS:
  2.     try:
  3.         if obj.EffectiveName == "TOTAL" or obj.EffectiveName == "KNF" or obj.EffectiveName == "INCOMER" or obj.EffectiveName == "AUTOMAT" or obj.EffectiveName == "LINE":
  4.             obj.Delete()
  5.     except:
  6.         time.sleep(0.1)
  7.         if obj.EffectiveName == "TOTAL" or obj.EffectiveName == "KNF" or obj.EffectiveName == "INCOMER" or obj.EffectiveName == "AUTOMAT" or obj.EffectiveName == "LINE":
  8.             obj.Delete()
  9.  

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Python & ActiveX/COM Autocad
« Ответ #498 : 07-04-2022, 23:25:08 »
Я так понимаю, цикл надо примерно так переписать?
Нет. Во-первых, выше я написал как нужно организовать цикл (от последнего элемента к первому). Во-вторых, нужен какой-то счетчик количества повторений при ошибке (например, не больше 100). Иначе возможно зацикливание.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Derie1

  • ADN OPEN
  • Сообщений: 13
  • Карма: 0
Re: Python & ActiveX/COM Autocad
« Ответ #499 : 08-04-2022, 17:05:48 »
В итоге поставил задержку 2 секунды перед строчкой:

Код - Python [Выбрать]
  1. time.sleep(2)
  2. objSS = doc.SelectionSets.Add("toErase")
  3.  

Полностью это проблему не решило, но ошибок стало значительно меньше.
Кстати, ни разу не видел, а возможно ли сделать дополнительный SelectionSet, отобрать в него только те элементы, которые надо удалить, и потом разом их все удалить, не перебирая циклом каждый?

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Python & ActiveX/COM Autocad
« Ответ #500 : 08-04-2022, 17:15:43 »
Кстати, ни разу не видел, а возможно ли сделать дополнительный SelectionSet, отобрать в него только те элементы, которые надо удалить, и потом разом их все удалить, не перебирая циклом каждый?
Если мне не изменяет память, то objSS.Erase() должно удалить все примитивы из набора.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн dlobyntsev

  • ADN Club
  • Сообщений: 18
  • Карма: 8
Re: Python & ActiveX/COM Autocad
« Ответ #501 : 11-04-2022, 18:12:20 »
pywintypes.com_error: (-2147418111, 'Вызов был отклонен.', None, None)
Это ошибка COM в Windows, характерна не только для AutoCAD, но и для других приложений, например, Excel. Зависит от кучи разных вещей, начиная от версии Windows и её обновлений, версии AutoCAD и заканчивая быстродействием компьютера, не нашёл какой-то стабильной закономерности. Причина, насколько я понимаю в том, что COM не поддерживает очередь обработки, и, похоже, если AutoCAD занят в то время, кода ему выдается новая команда, то запрос отклоняется. Естественно, что добавление задержек в позволяет уменьшить проблему, но это лишь лечение симптомов.
Более-менее работающий вариант на базе кода со Stack Overflow за авторством Erik Sällström, который, однако, не даёт 100% результата:

Код - Python [Выбрать]
  1. import win32com.client
  2. from pywintypes import com_error
  3. from time import sleep
  4.  
  5. #Настройки задержек
  6. _DELAY = 0.1  # секунд
  7. _TIMEOUT = 20.0  # секунд
  8.  
  9. #Создаем служебную функцию
  10. def _com_call_wrapper(f, *args, **kwargs):
  11.     """
  12.    Повторяет вызов при получении ошибки 'Call was rejected by callee/Вызов был отклонён.'
  13.    Повторение выполняется с задержкой n*_DELAY секунд где n - количество ошибок при обращении к COM. Повторение до тех пор, пока задержка не станет равной _TIMEOUT
  14.    """
  15.     # Обрабатываем аргументы
  16.     args = [arg._wrapped_object if isinstance(arg, ComWrapper) else arg for arg in args]
  17.     kwargs = dict([(key, value._wrapped_object)
  18.                    if isinstance(value, ComWrapper)
  19.                    else (key, value)
  20.                    for key, value in dict(kwargs).items()])
  21.  
  22.     start_time = None
  23.     n = 1
  24.     while True:
  25.         try:
  26.             result = f(*args, **kwargs)
  27.         except com_error as e:
  28.             if e.hresult == -2147418111:
  29.                 if start_time is None:
  30.                     start_time = time.time()
  31.                 elif time.time() - start_time >= _TIMEOUT:
  32.                     raise
  33.                 time.sleep(_DELAY*n)
  34.                 n += 1
  35.                 continue
  36.             raise
  37.         break
  38.  
  39.     if isinstance(result, win32com.client.CDispatch) or callable(result):
  40.         return ComWrapper(result)
  41.     return result
  42.  
  43. class ComWrapper(object):
  44.     """
  45.    Объект-обёртка, который позволяет перехватывать вызовы к исходному объекту и оборачивать их в служебную функцию _com_call_wrapper
  46.    """
  47.  
  48.     def __init__(self, wrapped_object):
  49.         assert isinstance(wrapped_object, win32com.client.CDispatch) or callable(wrapped_object)
  50.         self.__dict__['_wrapped_object'] = wrapped_object
  51.  
  52.     def __getattr__(self, item):
  53.         return _com_call_wrapper(self._wrapped_object.__getattr__, item)
  54.  
  55.     def __getitem__(self, item):
  56.         return _com_call_wrapper(self._wrapped_object.__getitem__, item)
  57.  
  58.     def __setattr__(self, key, value):
  59.         _com_call_wrapper(self._wrapped_object.__setattr__, key, value)
  60.  
  61.     def __setitem__(self, key, value):
  62.         _com_call_wrapper(self._wrapped_object.__setitem__, key, value)
  63.  
  64.     def __call__(self, *args, **kwargs):
  65.         return _com_call_wrapper(self._wrapped_object.__call__, *args, **kwargs)
  66.  
  67.     def __repr__(self):
  68.         return 'ComWrapper<{}>'.format(repr(self._wrapped_object))
  69.  
  70. # Подключение к AutoCAD осуществляем через объект ComWrapper
  71.  
  72. acad = ComWrapper(win32com.client.dynamic.Dispatch("AutoCAD.Application"))
  73.  

Если задачу удастся решить корректно, обязательно сюда выложу.

upd: похоже, полностью корректное решение найдено. Протестирую - выложу для использования.
« Последнее редактирование: 13-04-2022, 16:17:56 от dlobyntsev »

Оффлайн dlobyntsev

  • ADN Club
  • Сообщений: 18
  • Карма: 8
Re: Python & ActiveX/COM Autocad
« Ответ #502 : 15-04-2022, 16:50:46 »
Всем доброго дня. Думаю, каждый, кто работает с автокадом из чистого питона, сталкивались с ошибкой -2147418111 "Call was rejected by callee/Вызов был отклонен". Плохой вариант борьбы с данной ошибкой - введение в код задержек. Я реализовал и хороший вариант - перехват ошибок при любом обращении к автокаду. Для удобства собрал модуль pyacadcom. Устанавливается стандартно: pip install pyacadcom.

В вашем скрипте:
Код - Python [Выбрать]
  1. #импортируем модуль
  2. import pyacadcom
  3.  
  4. #создаём объект автокада, результат аналогичен app = win32com.client.dynamic.Dispatch("AutoCAD.Application")
  5. app = pyacadcom.AutoCAD()
  6.  
  7. #Обращаемся к объекту автокада в соответствии с объектной моделью, например
  8. adoc = app.ActiveDocument #получение активного документа автокада
  9.  

При этом все ошибки -2147418111 перехватываются, никаких задержек в коде ставить не нужно.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Python & ActiveX/COM Autocad
« Ответ #503 : 15-04-2022, 16:58:43 »
Думаю, каждый, кто работает с автокадом из чистого питона, сталкивались с ошибкой -2147418111 "Call was rejected bycallee/Вызов был отклонен".
Вообще-то ошибок, связанных с "занятостью" AutoCAD несколько: https://adn-cis.org/kak-ispolzuya-visual-c-zapustit-autocad-i-zastavit-ego-vyipolnyat-nekotoryie-dejstviya.html
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн dlobyntsev

  • ADN Club
  • Сообщений: 18
  • Карма: 8
Re: Python & ActiveX/COM Autocad
« Ответ #504 : 15-04-2022, 17:26:50 »
Это не ошибка "занятости" автокада. Это ошибка, причиной которой является взаимодействие с COM через модуль pywin32. Не поддерживается очередь команд, и, если новый запрос поступает до отработки предыдущего, новый запрос отклоняется. Для меня это было огромной головной болью, потому что появление этой ошибки зависит отчего угодно, вплоть до фазы Луны. Если посмотреть на вопросы людей в этой теме - явно такие же проблемы. Пока данный модуль решил все вылеты моих скриптов. Спасибо за наводку, на всякий случай включу и эти ошибки в обработчик - это просто.

Оффлайн Derie1

  • ADN OPEN
  • Сообщений: 13
  • Карма: 0
Re: Python & ActiveX/COM Autocad
« Ответ #505 : 15-04-2022, 17:45:29 »
Для удобства собрал модуль pyacadcom.
Спасибо за наводку. Попробовал в своём скрипте заменить:
Код - Python [Выбрать]
  1. acad = win32com.client.Dispatch("AutoCAD.Application")
  2.  
на строчку из библиотеки:
Код - Python [Выбрать]
  1. acad = pyacadcom.AutoCAD()
  2.  

По результатам теста все равно случаются ошибки, причем в тех же местах что и раньше. Думаю, всё дело в том, что в части функций так и остаются запросы через win32com.client, например, фильтр для создания SelectionSet:
Код - Python [Выбрать]
  1. objSS = doc.SelectionSets.Add("toErase")
  2.  
  3. FilterType = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_I2, [0])
  4. FilterData = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_VARIANT, ['INSERT'])
  5. SELECT_ALL = 5
  6.  
  7. objSS.Select(SELECT_ALL, pythoncom.Empty, pythoncom.Empty, FilterType, FilterData)
  8.  
Так же в задании точки в модели используется:
Код - Python [Выбрать]
  1. def POINT(x, y, z):
  2.     point = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, (x, y, z))
  3.     return point
  4.  
Почему то, при замене этой функции на аналог из pyacadcom:
Код - Python [Выбрать]
  1. def POINT(x, y, z):
  2.     point = pyacadcom.acadPoint(x, y, z)
  3.     return point
  4.  
блок вставляться отказываются, при этом получаю следующую ошибку:
Цитировать
total_blk = ms.InsertBlock(pt1, "TOTAL", 1.0, 1.0, 1.0, 0)
  File "<COMObject <unknown>>", line 3, in InsertBlock
TypeError: must be real number, not acadPoint

Эти проблемы возможно решить с использованием pyacadcom?

Оффлайн dlobyntsev

  • ADN Club
  • Сообщений: 18
  • Карма: 8
Re: Python & ActiveX/COM Autocad
« Ответ #506 : 15-04-2022, 17:58:38 »
objSS = doc.SelectionSets.Add("toErase")
Как получен doc? Если это не производное от pyacadcom.AutoCAD() - ошибки перехватываться не будут. Какой код ошибки?
класс acadPoint в pyacadcom в текущем варианте возвращает координаты в формате, подходящим для автокада при использовании acadPoint.coordinates:
Код - Python [Выбрать]
  1. def POINT(x, y, z):
  2.     point = pyacadcom.acadPoint(x, y, z)
  3.     return point.coordinates
  4.  

Оффлайн Derie1

  • ADN OPEN
  • Сообщений: 13
  • Карма: 0
Re: Python & ActiveX/COM Autocad
« Ответ #507 : 15-04-2022, 18:04:53 »
Как получен doc?
Код - Python [Выбрать]
  1. acad = pyacadcom.AutoCAD()
  2. doc = acad.Documents.Open(dwg_file)
  3. ms = doc.ModelSpace
  4.  
Заменил создание точки, так работает, спасибо!
Код - Python [Выбрать]
  1. def POINT(x, y, z):
  2.     return pyacadcom.acadPoint(x, y, z).coordinates
  3.  

Оффлайн dlobyntsev

  • ADN Club
  • Сообщений: 18
  • Карма: 8
Re: Python & ActiveX/COM Autocad
« Ответ #508 : 15-04-2022, 18:10:05 »
acad = pyacadcom.AutoCAD()
doc = acad.Documents.Open(dwg_file)
ms = doc.ModelSpace
Вообще должно работать. Напишите коды ошибок, которые получаются.

Оффлайн Derie1

  • ADN OPEN
  • Сообщений: 13
  • Карма: 0
Re: Python & ActiveX/COM Autocad
« Ответ #509 : 15-04-2022, 21:10:28 »
Вообще должно работать. Напишите коды ошибок, которые получаются.
На всякий случай, привожу весь скрипт по удалении определенных блоков из чертежа:
Код - Python [Выбрать]
  1. import win32com.client
  2. import pythoncom
  3. from PyQt5 import QtWidgets
  4. import pyacadcom
  5.  
  6.  
  7. app = QtWidgets.QApplication([])
  8. # acad = win32com.client.Dispatch("AutoCAD.Application")
  9. acad = pyacadcom.AutoCAD()
  10. dwg_file = QtWidgets.QFileDialog.getOpenFileName(caption="Выберите файл шаблона или схемы в AutoCAD... ",
  11.                                                  filter="DWG (*.dwg)")[0]  # выбираем исхоный файл
  12. doc = acad.Documents.Open(dwg_file)
  13.  
  14. try:
  15.     for i in doc.SelectionSets:
  16.         i.Delete()
  17. except:
  18.     pass
  19.  
  20. objSS = doc.SelectionSets.Add("blocks")
  21.  
  22. FilterType = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_I2, [0])
  23. FilterData = win32com.client.VARIANT(
  24.     pythoncom.VT_ARRAY | pythoncom.VT_VARIANT, ['INSERT'])
  25. SELECT_ALL = 5
  26.  
  27. objSS.Select(SELECT_ALL, pythoncom.Empty,
  28.              pythoncom.Empty, FilterType, FilterData)
  29. print("\n")
  30.  
  31. for obj in objSS:
  32.     if obj.EffectiveName == "TOTAL" or obj.EffectiveName == "KNF" or obj.EffectiveName == "INCOMER" or obj.EffectiveName == "AUTOMAT" or obj.EffectiveName == "LINE":
  33.         obj.Delete()
  34.  
  35. objSS.Delete()
  36.  

Ошбика 1 (при создании SelSet):
Цитировать
Traceback (most recent call last):
  File "c:\Users\mdavy\Dropbox\MyProg\SLD_Builder\.test_code\deleting_selected_blocks.py", line 20, in <module>
    objSS = doc.SelectionSets.Add("blocks")
  File "C:\Users\mdavy\AppData\Local\Programs\Python\Python310\lib\site-packages\win32com\client\dynamic.py", line 628, in __getattr__
    ret = self._oleobj_.Invoke(retEntry.dispid, 0, invoke_type, 1)
pywintypes.com_error: (-2147418111, 'Вызов был отклонен.', None, None)
Ошибка 2 (там же, но другая):
Цитировать
Traceback (most recent call last):
  File "c:\Users\mdavy\Dropbox\MyProg\SLD_Builder\.test_code\deleting_selected_blocks.py", line 20, in <module>
    objSS = doc.SelectionSets.Add("blocks")
  File "C:\Users\mdavy\AppData\Local\Programs\Python\Python310\lib\site-packages\win32com\client\dynamic.py", line 639, in __getattr__
    raise AttributeError("%s.%s" % (self._username_, attr))
AttributeError: <unknown>.Add
Ошибка 3 (при удалении блоков. Здесь может быть ошибка на любом блоке, часть удалит, а потом где то встрянет. Так же здесь случаются ошибки как и в случае 1):
Цитировать
Traceback (most recent call last):
  File "c:\Users\mdavy\Dropbox\MyProg\SLD_Builder\.test_code\deleting_selected_blocks.py", line 32, in <module>
    if obj.EffectiveName == "TOTAL" or obj.EffectiveName == "KNF" or obj.EffectiveName == "INCOMER" or obj.EffectiveName == "AUTOMAT" or obj.EffectiveName == "LINE":   
  File "C:\Users\mdavy\AppData\Local\Programs\Python\Python310\lib\site-packages\win32com\client\dynamic.py", line 639, in __getattr__
    raise AttributeError("%s.%s" % (self._username_, attr))
AttributeError: <unknown>.EffectiveName
Ошбика 4 (при добавлении всех блоков в SelSet):
Цитировать
Traceback (most recent call last):
  File "c:\Users\mdavy\Dropbox\MyProg\SLD_Builder\.test_code\deleting_selected_blocks.py", line 27, in <module>
    objSS.Select(SELECT_ALL, pythoncom.Empty,
  File "C:\Users\mdavy\AppData\Local\Programs\Python\Python310\lib\site-packages\win32com\client\dynamic.py", line 639, in __getattr__
    raise AttributeError("%s.%s" % (self._username_, attr))
AttributeError: Add.Select

Ну и так далее. При этом у меня на домашнем компе, код намного более стабильно работает, чем на рабочем..