Всем привет! Решил взять небольшую паузу по поводу технических статей, и написать ещё одно размышление. Сегодня — хейтерская статья.
1. Разная реализация похожих вещей.
Хороший пример: размещение групп и сборок. Вроде бы при размещении руками последовательность действий примерно одинаковая. Но при размещении из кода — нет. Сборку мы размещаем статическим методом класса Assembly Create, а группу — методом PlaceGroup в ItemFactory. Я понимаю, когда в этот класс вынесено создание экземпляров семейств, потому что там есть множество различных вариантов. Но создание экземпляра группы банально происходит по двум аргументам.
2. Фатальные ошибки в неожиданных местах
Отдадим должное разработчикам Revit API: в большинстве случаев они предусмотрели все нюансы, и код выбрасывает исключение, если мы хотим сделать что-то неправильное, и это исключение довольно информативное и по нему можно понять, что не так (хотя это понимание приходит постепенно с опытом). Но некоторые вещи остались непродуманными, из-за чего происходят баги, которые потом приходится долго и нудно отлавливать. Приведу два примера:
- Нет защиты от случайного переполнения стека (если мы в поле get для свойства сделали не обращение к приватному полю этого свойства, а обращение к самому свойству (нередкая опечатка, ведь имена поля и свойства похожи), то мы получим краш Revit, а не завершение работы плагина, где возникла это ошибка.
- Если мы создали кастомный DockablePane (это панель по типу "Свойств" или "Диспетчера проектов" и хотим вызвать из него команды, то почти при любом исключении мы рискуем получить фатальную ошибку. Нужно подстраховываться через try-catch, но в документации об этом не пишут.
3. Непонятная реализация пользовательских панелей
Да-да, вернёмся опять к DockablePane. Однажды мне потребовалась создать такую панель, и в итоге нормальные ответы, как это всё работает, я нашёл только в четвёртом по счёту прочитанном блоге. Примеры из остальных были хорошо написаны, но не срабатывали, а с сайта Autodesk из полезного я почерпнул только то, что следует использовать Page вместо Window. Ну, и на том спасибо.
При этом нет никакого технического ограничения в том, чтобы вызывать внешние команды из DockablePane. Но Revit делать этого не разрешает: выдаёт исключение "вы не находитесь в контексте API". Поэтому мне пришлось использовать костыльное решение: я передаю информацию о необходимости выполнить команду в событие Idling внутри класса Application (где добавляю пользовательские кнопки). Это событие возникает во время паузы в программе, выполняет команду и обнуляет переданную информацию. Решение неочевидное и не факт, что оптимальное. Но почему бы просто не сделать нормальную документацию с примерами, как это всё должно делаться, чтобы не заставлять разработчиков страдать?
4. Необобщённая реализация GetEnumerator в некоторых местах
Об этой проблеме я писал здесь. В общем, небольшая проблема, но она свидетельствует о том, что в API есть недоработки. Ну серьёзно, какие объекты помимо категорий могут входить в CategorySet? Такая же история наблюдается и с ConnectorSet, и с ElementSet — возвращается object вместо нужного нам элемента.
В начале своего пути в разработке для Revit я ещё не знал, что цикл foreach — просто удобная обёртка для реализаций интерфейса IEnumerable, и когда мне надо было получить коннектор, создавал ConnectorSetIterator, вызывал MoveNext() и т.д. Как оказалось, во-первых, можно было использовать foreach, а во-вторых — у класса ConnectorManager есть метод Lookup, который позволяет получить коннектор ещё проще.
5. Отсутствие некоторых возможностей в API
Должен признаться, что эта проблема постепенно устраняется. API 2024 гораздо более полный, чем API 2019 — с этим отчасти связано то, что многие разработчики плагинов прекращают поддержку версий младше 2020. Не потому что им лень, а потому что в 2019 и ранее многое просто нельзя реализовать.
Например, в 2023 году появилось событие SelectionChanged, информирующее о том, что что-то в проекте было выделено (теперь можно написать свою панель Свойств в плагине). В ранних версиях узнать это мы не могли.
Но тем не менее, остались некоторые моменты, которые нельзя реализовать. Вот мои два примера:
1. Есть событие DocumentClosing, и мы можем отменить закрытие (например, спросив у пользователя дополнительно что-либо) — тут всё окей. И есть событие ApplicationClosing. Но его отменить мы никак не можем (хотя пользователь может закрыть документ, а может приложение). И при закрытии приложения возникнут несколько DocumentClosing, мы их отменим, но документ всё равно закроется, так как было закрыто приложение.
2. Некоторые пользовательские команды из интерфейса Ревит нельзя реализовать через API. Например, Обрезать/Удлинить до угла: это было бы идеальное решение в плагине по построению стен, но так нельзя, поэтому приходится изобретать велосипед, создавая новые методы, выполняющие ту же работу.
Заключение
На этом на сегодня всё. Если вы заметили где-то ошибку, опечатку, или вдруг знаете, что я не прав (мало ли, вдруг я что-то не так понял, или в 2024 всё поменялось, пока я сижу в 2023 Ревите), или вы знаете, чем дополнить — смело пишите в комментарии. И не забывайте подписываться на мой телеграм-канал. До новых встреч!