В этой статье покажу, как с помощью кода в Питоне определить, в каком пространстве находятся потребители в инженерных системах на примере воздухораспределителей. Определим принадлежность элемента и запишем ему в параметр номер и имя пространства. Важный момент — речь про элементы, которые размещаются точечно и «знают» принадлежность к пространству, просто не выводят это сами в параметры. Например, потребители инженерных систем.

Вот таким будет наш результат

Я уже разбирал подобную тему, но делал её более «тупым» способом: через пересечение геометрии пространств и элементов. Способ годный, если не уметь писать код в Питоне, так как он хоть и медленный, но рабочий. Теперь же покажу более быстрый, но сложный способ для новичков.

Для работы нам понадобится плагин Revit Lookup, скачайте его с Гитхаба проекта для своей версии Ревита и установите. Буду показывать на примере Ревите 2023, но в целом способ подходит для любой версии.

Подготовка

Я не просто так взял именно эту категорию элементов, для этого есть веская причина: у меня есть модель с воздухораспределителями. А, ну ещё потому что это элементы, которые размещают высоко, поэтому есть риск, что они будут за пределами пространства. И вот этого допускать нельзя.

Что же делать, если семейство «вылезает» за границы пространства своей геометрией? Нужно зайти в редактор семейства, включить ему точку принадлежности к помещению и опустить пониже. Там нет точных значений, опускайте примерно на глаз. Цель — чтобы эта точка оказалась внутри пространства в вашей модели.

В старых версиях эта галочка называлась «Точка расчета площади»

Теперь выделяем диффузор и жмём в Ревит Лукапе Snoop Current Selection, исследовать текущий выбор. Появится окошко со свойствами воздухораспределителя, мотаем до параметра «Space» — это и есть пространство. Если там есть имя и номер пространства, значит, всё работает корректно, осталось кодом вытащить эти данные и записать в параметр диффузора.

Если вы работаете в модели без пространств и подтягиваете помещения из связанного файла, то тут мой код вам не поможет, он только для случаев, когда работаете с пространствам. Ревит не знает, в каком помещении из связанного файла находится элемент. Если работаете в файле, где есть помещения, то по аналогии можно делать то же самое, но с помещениями.

У меня кнопка Ревит Лукапа на панели быстрых команд, потому что я капец какой мамкин программист
Обратите внимание: тут два параметра с именем Space

Начало скрипта

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

Добавляю нод с Питоном Скриптом, вставляю в него заготовку кода и далее сразу введу переменную, в которую буду собирать все воздухораспределители из модели.

import clr

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager as DM

from RevitServices.Transactions import TransactionManager as TM

from System import Guid

doc = DM.Instance.CurrentDBDocument

# Список элементов для определения принадлежности к пространству
elements_for_location = UnwrapElement(IN[0])

Метод UnwrapElement нужен, чтобы нормально обрабатывать в Динамо Ревитовские элементы. Это какие-то программистские тонкости, в этом разбираться необязательно, а вот метод использовать надо.

Основной код

У нас на входе одномерный список элементов, то есть нет никаких вложенностей, просто «ровный» список элементов. Поэтому нам нужен один цикл for для обработки всех элементов. Перед этим сразу создам пустой список, в который буду записывать номер и имя пространства, его же и подам на выход из Питон-нода. Кроме него, сделаю ещё один пустой список для элементов, которые находятся в пространствах. Ведь у нас могут быть наружные решётки и зонты, которые лежат вне пространств.

# пустые списки для записи и вывода данных: имя и номер пространства и элементы в пространствах
number_and_name_of_space = []
elements_in_spaces = []

Дальше циклом берём каждый элемент, определяем, в каком он пространстве. Если он в пространстве, то мы его получим. Если он вне какого-либо пространства, то мы получим null. Соответственно, нужно проверить, что там такое вышло. Это можно сделать простым условием IF. Если элемент не в пространстве, то в переменной будет значение null, при проверке условием if оно выдаст false.

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

Затем можно либо нодами записать значения, либо тут же в Питоне через транзакцию. Давайте будем делать через транзакцию.

Итак, создаю цикл, беру один элемент, получаю с него пространство. Надёжнее всего получать пространство с указанием стадии элемента, хотя если повезёт, то и без стадии можно получить.

  • В CPython3 писать надо вот так: el_space = element.get_Space(elem_phase)
  • В IronPython 2 писать надо вот так: el_space = element.Space[elem_phase]
  • Вообще без указания фазы пишем так: el_space = element.Space

На выходе получим либо пространство, либо null, поэтому делаю проверку условием:

for element in elements_for_location: # беру циклом каждый элемент
    
    elem_phase = doc.GetElement(element.CreatedPhaseId) # получаю стадию с элемента

    el_space = element.get_Space(elem_phase) # получаю пространство с элемента, это версия для CPython3 в Ревитах 2022+
    
    # el_space = element.Space[elem_phase]
    
    if el_space: # делаю проверку, действительно ли элемент в пространстве   
    
        space_name = el_space.get_Parameter(BuiltInParameter.ROOM_NAME).AsString() # получаю имя пространства
        space_number = el_space.Number # получаю номер пространства
    
        space_info = space_number + " - " + space_name # формирую значение для номера и имени пространства
        number_and_name_of_space.append(space_info) # записываю в список значений для вывода из нода 
        
        elements_in_spaces.append(element) # записываю в список элемент из пространства

Как видите, номер у пространства взять легко, а для имени код длиннее, хотя в Лукапе у пространства есть отдельное свойство «Name». Оно почему-то не сработало, ну и фиг с ним: берём значение через get_Parameter. Имя — встроенное свойство пространства, поэтому я беру имя параметра через BuiltInParameter и через точку пишу это встроенное свойство.

Чтобы его посмотреть, нужно провалить в пространство, а затем в его Parameters в Лукапе. Ищем параметр «Имя» и заходим в его Definition, там и сидит встроенное имя.

Код space_number + " - " + space_name делает вот что: он берёт номер и имя пространства и соединяет их через дефис с пробелами. Это обычная конкатенация, суммирование строк. Записываю это значение в переменную space_info.

Теперь нужно это значение записать в параметр элемента. Давайте возьмём для этого общий параметр «ADSK_Примечание». У общих параметров есть незадача — имена у них могут быть одинаковыми, только GUID уникальный. Чтобы точно не ошибиться, будем писать в параметр по его Гуиду. Поэтому сперва посмотрим его в Лукапе.

Заходим в иконку Лукапа, выбираем Snoop Database... Сбоку находим SharedParameterElement и раскрываем список. Тут ищем наш параметр и смотрим в свойство «GuidValue», оно в конце списка. Жмём правой кнопкой и копируем в буфер.

Теперь в коде нужно сделать три вещи: открыть транзакцию, провести действие, закрыть транзакцию. Транзакция как бы позволяет «вылезти» из Динамо в Ревит и проводить действия над элементами в модели. После выводим из Питон-нода информацию и готово.

Код будет такой:

        TM.Instance.EnsureInTransaction(doc) # Открытие транзакции
        element.get_Parameter(Guid('a85b7661-26b0-412f-979c-66af80b4b2c3')).Set(space_info)
        TM.Instance.TransactionTaskDone() # Закрытие транзакции
        
OUT = number_and_name_of_space, elements_in_spaces

Чтобы задать значение, мы берём элемент, как бы называем параметр, куда будет писать значение, а потом пишем метод Set и в скобках значение или параметр со значением.

Результат работы

Запускаю — скрипт отрабатывает. Составляю спеку на воздухораспределители и вывожу туда наименование и ADSK_Примечание. На скриншоте видно, что некоторые решётки и зонт не обработались — все они лежат вне пространств, поэтому у них пусто.

Вот весь код с комментариями:

import clr

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

clr.AddReference('RevitNodes')
import Revit

clr.AddReference('RevitServices')

from RevitServices.Persistence import DocumentManager as DM

from RevitServices.Transactions import TransactionManager as TM

from System import Guid

doc = DM.Instance.CurrentDBDocument

# Список элементов для определения принадлежности к пространству
elements_for_location = UnwrapElement(IN[0])

# пустые списки для записи и вывода данных: имя и номер пространства и элементы в пространствах
number_and_name_of_space = []
elements_in_spaces = []

for element in elements_for_location: # беру циклом каждый элемент
    
    elem_phase = doc.GetElement(element.CreatedPhaseId) # получаю стадию с элемента

    el_space = element.get_Space(elem_phase) # получаю пространство с элемента, это версия для CPython3 в Ревитах 2022+

    # el_space = element.Space[elem_phase]
    
    if el_space: # делаю проверку, действительно ли элемент в пространстве   
    
        space_name = el_space.get_Parameter(BuiltInParameter.ROOM_NAME).AsString() # получаю имя пространства
        space_number = el_space.Number # получаю номер пространства
    
        space_info = space_number + " - " + space_name # формирую значение для номера и имени пространства
        number_and_name_of_space.append(space_info) # записываю в список значений для вывода из нода 
        
        elements_in_spaces.append(element) # записываю в список элемент из пространства
        
        TM.Instance.EnsureInTransaction(doc) # Открытие транзакции
        element.get_Parameter(Guid('a85b7661-26b0-412f-979c-66af80b4b2c3')).Set(space_number + " - " + space_name) # в этом Гуиде сидит ADSK_Примечание
        TM.Instance.TransactionTaskDone() # Закрытие транзакции
        
OUT = number_and_name_of_space, elements_in_spaces

Кто у мамы программист? Ты, мой хороший, ты.