Dynamo: aktualizacja (refactoring?) skryptu do malowania ścian


11.12.2020

Zadanie miało być dość proste – wziąć jeden ze swoich starych skryptów, opracowanych jeszcze podczas pracy za granicą, przetłumaczyć jego dokumentację i umieścić na stronie. Planowałem poświęcić na to godzinę, wyszło „jak zwykle” – czyli zajęło mi to więcej czasu. Ale od początku!

Ponad dwa lata temu zostałem postawiony przed następującym problemem: podwykonawcy dostarczyli nam model szpitala, który zostanie wybudowany na terenie Wielkiej Brytanii, a my musieliśmy ocenić/oszacować ile i jakiej farby zostanie użyte na wszystkie piętra. Farba nakładana była tylko na ściany działowe (grubość równa bądź niższa niż 200 mm), rodzaj farby różnił się w zależności od pomieszczeń, a te były powiązane z piętrami. Proste zadanie? Niekoniecznie, ponieważ moduł do nakładania farby, tak jak wiele innych w Revicie – został „zapożyczony” z innego oprogramowanie (prawdopodobnie 3Ds Max), przez co jego używanie może odbiegać od tego co zazwyczaj robimy w Revicie. W tym konkretnym przypadku – dość archaiczne menu wymaga ode mnie zaznaczanie pojedynczych elementów.

W takich momentach decyduję się na użycie Dynamo, i cały swój tok rozumowania umieściłem na swojej stronie internetowej (tutaj: http://dconstruct.info/2017/07/17/painting-walls-using-dynamo/). Skrótowo – nie wszystkie działania były do wykonania w „czystym” Dynamo, i potrzebny był Python.

Oczywiście, to było dwa lata temu. A jak jest teraz? Zobaczmy!

Tak ten skrypt wyglądał w oryginale:

Węzły List.FilterByBoolMask dzielą całość na dwie wyraźne części. Do tego podziału dodałbym jeszcze część „skrajnię lewą” – czyli nasz logiczny input, oraz część „skrajnię prawą”, w której mieści się kod Pythonowy. Jak się okazuję, teraz zrobiłbym inaczej praktycznie wszystko, jedynie skrajnie lewa przetrwała próbę czasu.

Fragment pierwszy – do pierwszego List.FilterByBoolMask – ma za zadanie selekcjonować elementy kategorii ściana w zależności od piętra, z jakim są związane. Dwa lata temu dokonałem tego w sposób poprawny, jednak dość nieelegancki. Ponieważ nie udawało mi się dokonać porównania (testu logicznego dającego odpowiedź prawda/fałsz) „Poziomu” z Parametrem poziomu – uznałem, że jedynym logicznym zagraniem będzie zamiana tego na wartość typu string, po czym porównuję występowanie jednej w drugiej. Tu zadziałało, ale jest to podatne na tzw. false positive, czyli poprawnie test przejdą elementy, które zawierają daną frazę, jednak są innym poziomem (np. Level 1 Mezzanine pasujące do “Level 1”).

Jak zrobiłbym to dziś?

Co się zmieniło? Tym razem pytam nie o „Parameter”, a o „ParameterValue”, następnie – porównuję nie przy pomocy „==” (który to radzi sobie z tekstem i liczbami, ale nie obiektami Revit), ale przez węzeł bezpośrednio do takich porównań przeznaczony – List.Equals.

Przejdźmy do drugiego segmentu filtrującego. Jego zadaniem jest wyszukanie i odrzucenie wszystkich ścian, których szerokość jest większa niż 200 mm – tych grubszych nie uznajemy za działowe.

Co zmieniło się tutaj?

Wcześniej, z jakiegoś powodu (nie pamiętam, czy była to limitacja Dynamo, czy też może poziom moich umiejętności) miałem problem z uzyskaniem parametru „szerokość ściany” z wybranych elementów. Oczywiście, wynikało to z faktu, że parametr ten przypisany jest nie do wystąpienia, a do typu!

Wtedy to posłużyłem się dwoma dość prostymi węzłami z kodem Python, odpowiednio:

oraz:

Teraz starałbym się jeszcze mocniej, by używać Pythona jak najmniej. Uzasadnienie tego, nie wchodząc w techniczne szczegóły, nie jest proste. Jednak każdy, kto chociaż trochę miał do czynienia z mieszkanką DesignScript z Pythonem wie, że występują czasem problemy z kolejnością wykonywania, jak również z pamięcią podręczną – w przypadku ponownego użycia skryptu z innymi danymi wejściowymi. Co więcej, skrypt oparty o węzły jest prostszy do zrozumienia dla początkującego użytkownika Dynamo, przez co też prościej jest go aktualizować/utrzymywać w przyszłości.

Resztę pozostawiłbym bez zmian. Możliwe, że mógłbym też poprawić „wyjątki”, tj. sytuacje gdy wartość „width” wynosi null, jednak na ten moment to rozwiązanie „działa, więc nie ruszaj!”.

Ostatnim, kluczowym fragmentem tego skryptu jest kod napisany w Pythonie, który wykorzystuje odwołania do API Revita do nałożenia farby. Sprawdziłem w stabilnym Dynamo 2.3, i z tego co widzę, taki węzeł nadal nie istnieje! Dlatego też dwa lata temu stworzyłem coś takiego:

Co jest nie tak z tym kodem?

Otóż nic! Robi dokładnie to, o co został poproszony, maluje wszystkie powierzchnie ściany… wszystkie sześć powierzchni!

Najprawdopodobniej jesteśmy zainteresowani wyłącznie malowaniem jednej bądź dwóch powierzchni. I nie jest to ani spód, ani też wierzch ściany, nie jest to też niewidoczne dla nas połączenie ściana-ściana.

Co możemy zrobić, by to naprawić? Po pierwsze, możemy zaobserwować, że Revit zawsze przypisuję pierwsze dwie powierzchnie jako powierzchnię wewnętrzną i zewnętrzną. Pozostałe cztery to powierzchnie „naokoło”, czyli te, które nas nie interesują.

Postanowiłem więc dodać prosty warunek „if”, musiałem też przypisać listę porządkową przy „rozbijaniu” elementu ściana na element „Faces” (powierzchnie). W celach kontrolnych stworzyłem też listę z wartościami „pomalowany” i „niepomalowany”. Tak bym mógł szybko sprawdzić, co zostało pomalowane, a co zostało pominięte. Po lekkich modyfikacjach skrypt wygląda następująco:

…a węzeł ten daje nam taką odpowiedź (OUT):

Skrypt poprawiony, artykuł napisany! Jestem ciekaw, czy jeśli przyjdzie mi przysiąść do tego zadania za kolejne dwa lata, to czy będę miał kolejne pomysły na poprawienie tego (bądź co bądź) prostego kodu. Czas pokaże.