В этом материале Костя расскажет, как настроить заглушки — специальные подсказки при вводе кода, которые помогают проанализировать свойства и методы объектов и быстро их ввести, а не печатать вручную. С помощью свойств мы получаем информацию, а с помощью методов — создаём или меняем объекты. Все свойства и методы не запомнить, и тут помогают заглушки.

Автор материала

Статью написал Костя @say_hey_to_k — БИМ-специалист из Китая. Он работает на канадскую фирму и, кроме Ревита, занимается также и программированием.

Это вторая часть из материала о настройки VSCode, первая была про установку и настройку интерфейса VSCode.

Заглушки

Заглушки нам нужны для упрощения работы с объектами Revit API. С их помощью можно смотреть, какие есть свойства и методы у классов Ревита, а также что в них нужно подать. И не лезть каждый раз в RevitLookup или на revitapidocs.com.

Например, у некоторые трубопроводных элементов может быть свойство «PipingNetwork». Когда работаешь с кодом нечасто, то можно забыть, как это свойство пишется: pipenetwork или pipingNetwork. И заглушка поможет «увидеть» варианты в выпадающем списке и из них выбрать правильное написание.

Вот пример для создания линии. Я ввожу имя переменной «Line» и дальше начинаю писать название метода для создания новой линии, а заглушки мне подсказывают, какие варианты возможно с этим объектом:

Начинаю вводить Create и сразу получаю подсказки

Когда выберу метод и открою скобочку, то заглушки покажет подсказку, что нужно вводить в метод. Для создания линии нужно указать точку начала и конца в формате XYZ, о чём подсказка и сообщает. Теоретически там могли бы быть не XYZ, а Point или ещё что-нибудь, но подсказка нам говорит: «Давай вводи XYZ, вот так будет правильно». И не надо лезть на сайт с Revit API и читать, что там этот метод ждёт на ввод.

Информация в подсказке, что нужно ввести в метод
Та же информация на сайте Ревит АПИ

То есть заглушки работают как своего рода справочник возможных значений, а также помогают быстрее вводить команды, так как из списка можно выбрать подходящий вариант, нажать по нему и он напечатается. Для длинных команд это очень удобно, замучаешься печатать этот GetMEPConnectorInfo, например.

Можно выбрать вариант и не печатать его самому

Скачивание и распаковка заглушек

Заходим на https://github.com/BIMOpenGroup/RevitAPIStubs, скачиваем архив с заглушками stubs.rar.

В целом, не важно, куда их распаковывать. Но имеет смысл положить в папку, которую в будущем можно будет переносить на другую Винду, компьютер и т. д.

К примеру, у меня они расположены в папке Stubs вместе со всем кодингом:

Настройка VSCode для работы с заглушками

Нам надо попасть в файл с пользовательскими настройками VSCode. Для этого нажимаем Ctrl+Shift+P и вбиваем settings. Находим меню «Параметры: Открыть пользовательские настройки (JSON)». Там видим знакомые нам из первой статьи настройки параметров flake8 и autopep8. Добавляем туда пути к заглушкам.

Можете скопировать нужные настройки отсюда, только не забудьте поменять пути к заглушкам на свои:

{
    "python.autoComplete.extraPaths": [
        "D:\\Coding\\Stubs\\BIMOpenGroup Stubs\\revit\\2023",
        "D:\\Coding\\Stubs\\BIMOpenGroup Stubs\\common",
    ],
    "python.analysis.extraPaths": [
        "D:\\Coding\\Stubs\\BIMOpenGroup Stubs\\revit\\2023",
        "D:\\Coding\\Stubs\\BIMOpenGroup Stubs\\common"
    ]
}

Важно: можно указывать только одну версию Ревита. Самая последняя версия заглушек для Ревита, что имеется на данный момент, — 2023. Хочется посвежее? Читайте заключение данной статьи.

Сохраняемся и перезапускаем VSCode.

Теперь, если вы импортируете в коде классы ревита, VSCode будет вам подсказывать:

import clr

clr.AddReference("RevitAPI")
from Autodesk.Revit import DB

DB.Element().loo — начинаем вводить метод и получаем подсказку

<< Здесь заканчивается статья для нормальных людей и начинается дрючево для задротов. Я вас предупредил. >>

Подсказки типов

В предыдущем примере мы сразу начали с элемента. Но чаще скрипт начинается с переменной, в которую что-то прилетает извне. Допустим, мы предполагаем, что туда прилетает элемент и мы дальше хотим с ним взаимодействовать, как с ревитовским элементом. Сам VSCode не может догадаться об этом, но мы можем ему подсказать.

Для этого напишем в комментарии переменной её тип:

import clr

clr.AddReference("RevitAPI")
from Autodesk.Revit import DB

element = UnwrapElement(IN[0])  # type: DB.Element
element.lo

Мы подсказали редактору кода, какого типа переменная, а он нам — какие атрибуты у неё есть. Как это работает? А вот так. Прогеры ещё во времена второго Питона договорились, что если для переменной в конце строки после двух пробелов написать # type:, а далее тип объекта, то редактор кода будет считать, что переменная этого типа. Причём, самому Питону на этот комментарий абсолютно наплевать. Это чисто между вами и редактором кода. Ну, и для mypy, если позанудничать. Но он всё равно не для нас. Даже ссылку на него давать не буду.

Для метода это работает так: после объявления метода пишете комментарий # type:, далее в скобочках какой тип в него прилетает, затем стрелочку -> и после неё, что метод возвращает. Если метод ничего не принимает, то оставляете скобки пустыми. Если метод ничего не возвращает, то после стрелочки пишете None.

В примере ниже мы подаём в метод элемент, а возвращаем строку.

import clr

clr.AddReference("RevitAPI")
from Autodesk.Revit import DB

element = UnwrapElement(IN[0])  # type: DB.Element

def get_element_name(elem):
    # type: (DB.Element) -> str
    return elem.Name

name = get_element_name(element)

Что мы с этого получаем? Во-первых, VSCode уже знает, что внутри метода имеет дело с элементом, и подскажет его методы. Во-вторых, поскольку мы указали, что метод выдаст строку, то VSCode уже знает, что переменная name, полученная при помощи нашего метода, — это строка. И будет подсказывать, какие методы уже есть у неё.

В-третьих, больше не надо сочинять тупые описания, вроде: «Этот метод делает то, что и написано в его названии, ещё он забирает вот такие штуки и возвращает вот эти штуки. А, может, и ничего не возвращает… как-то я об этом ещё не подумал. В общем, такой вот он молодец!».

Ваш метод должен говорить сам за себя, а подсказки типа покажут, что в него входит и что из него выходит! Всё равно вы потом его логику поменяете, а описание поменять забудете и будет ещё хуже (читаем «Чистый код» дяди Боба, и всё такое)! А иногда и подсказки даже лишние! Минутка гнева закончилась, продолжаем.

Когда возможно несколько вариантов типов, то их можно указать через разделитель |. Например, не у всех элементов есть параметр Name, а даже если и есть, то он может вернуть None. Так и запишем:

    def get_element_name(elem):
        # type: (DB.Element) -> str | None
        if hasattr(elem, 'Name'):
            return elem.Name
              
        name = get_element_name(element) 

Если навести на метод в месте использования, то увидим в подсказке какой тип он принимает и какие типы возвращает:

Когда в метод подаётся несколько аргументов, то их типы пишутся через запятую

def get_str_value(elem, param_name):
    # type: (DB.Element, str) -> str | None
    param = elem.LookupParameter(param_name)

    if param is not None:
        return param.AsString()

Для итерируемых объектов (вроде списка и словаря) можно указать, что за объект внутри, написав его в квадратных скобочках:

import clr

clr.AddReference("RevitAPI")
from Autodesk.Revit import DB

elems = UnwrapElement(IN[0])  # type: list[DB.Element]

for elem in elems:
    elem.

Пример чуть посложней. Мы указали, что в переменную elems приходит список элементов, а в переменную elems_per_mark будем запихивать строки в качестве ключей и список элементов в качестве значений.

import clr

clr.AddReference("RevitAPI")
from Autodesk.Revit import DB

elems = UnwrapElement(IN[0])  # type: list[DB.Element]
elems_per_mark = {}  # type: dict[str, list[DB.Element]]

for elem in elems:
    mark_param = elem.get_Parameter(DB.BuiltInParameter.ALL_MODEL_MARK)

    if mark_param is not None:
        mark = mark_param.AsString()

        if mark in elems_per_mark:
            elems_per_mark[mark].append(elem)

        else:
            elems_per_mark[mark] = [elem]

elems_per_mark

Я написал, что вышеуказанный способ подсказки типов придумали в Питоне 2. Следует добавить, что в Питоне 3.5 его поменяли (подробнее читайте здесь на сайте Питона). Поскольку большинство из нас, к сожалению, до сих пор работает со второй версией Питона, я не рекомендую использовать новый способ. Старый способ будет работать в новом питоне, а вот новый в старом не будет! VSCode всё равно понимает оба.

Допиливание заглушек

Мы знаем, что если у элемента вызвать метод LookupParameter, то должен выползти параметр. А значит, можно сразу через точку вызвать какой-нибудь метод параметра (хоть осторожные люди так и не делают). Но если попробуем это провернуть, то VSCode нам ничего нормального не подскажет:

import clr

clr.AddReference("RevitAPI")
from Autodesk.Revit import DB

elem = UnwrapElement(IN[0])  # type: DB.Element
elem.LookupParameter('Yo')

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

Если читать описание, то метод принимает self (игнорируем) и строку, а должен вернуть параметр. Но если смотреть на типы, а именно они нас сейчас и волнуют, то принимает он какой-то Any, а возвращает вообще None! Вот его-то методы нам и подсказали в предыдущем примере. Спасибо, блин!

Копнём чуть глубже, что же это за «заглушки» мы скачали. Мы скачали набор файлов, которые имитируют интересующие нас типы объектов и их методы, включая их описание. Их мы подсовываем редактору кода (через настройки) и как бы говорим, что если он увидит в нашем коде тип из заглушек, то вести этот тип будет себя так, как в них указано. Все методы внутри заглушек пустые. Выполняться файлы заглушек никогда не будут. Даже если попробовать, то ничего не получится.

Файлы заглушек были сгенерированы при помощи кода в исходниках репозитория, где мы их скачали. К сожалению, при генерации файлов не было учтено подсказки типов у атрибутов классов. Их нам и придётся допиливать своими ручками.

Итак, вспоминаем предыдущую статью и делаем Ctr + клик по методу LookupParameter (или F12). Он нас переносит в файл заглушки прямо в этот метод. Если с первого раза на нужный метод не перекинул, то, не закрывая файла заглушки, вернитесь в наш скрипт и нажмите F12 по этому методу ещё раз. Со второго раза должен перекинуть. Там видим его сгенерированное описание.

Добавляем нашу подсказку (не забываем, что может вернуться и None): # type: (str) -> Parameter | None

Сохраняем и закрываем файл заглушки. Перезапускаем окно VSCode. Можно закрыть и открыть VSCode, а можно нажать Ctrl+Shift+P, затем вбить «reload» и выбрать «Разработчик: Перезагрузить окно»:

После перезапуска пробуем ещё раз и радуемся или нет, если ничего не получилось.

Со свойствами класса — те, которые вызываются без скобочек, — поступаем немного по-другому. Они в заглушках записаны как переменные класса. Например, с Element.Name переходим к свойству через тот же F12, мотаем вправо и дописываем тип, как делали это для переменных:

Сохраняемся, перезапускаем окно. Теперь имя элемента возвращает строку, а не хрен пойми что.

Ещё можно добавлять, удалять и редактировать методы. Например, у элемента неверно сгенерировался метод get_BoundingBox. Так давайте его обновим!

    def get_BoundingBox(self, view):
        # type: (View | None) -> BoundingBoxXYZ | None
        """Retrieves a box that circumscribes all geometry of the element."""
        pass

То же можно проделать и с get_Parameter, get_Geometry, и т. д.

А ещё можно запариться на отличненько и замутить вот такую штуку (это сами как-нибудь додумайте):

Заключение

Таким образом, раз за разом, постепенно можно подправить самые часто используемые атрибуты классов. Геморр? Да.

«Но ведь можно же было сразу сгенерировать заглушки нормально со всеми этими вашими типами?», — спросите вы. Конечно, можно! Изучайте Дотнет, Питон, Гитхаб и становитесь контрибьютором генератора заглушек! Автор репы с удовольствием рассмотрит ваш PR и, в случае нормальной работоспособной либы, замержит ваш труд.

Конец второй части.