Categories
Tekla C#

Refleksja o dzieleniu się

Wpis [part not set] z 2 w serii Tekla - wyrównanie przekrojów

W poprzednim poście wspomniałem o GitHub. W budownictwie co chwila ‘jaramy’ się nowym platformami CDE do zarządzania dokumentacją w toku inwestycji. Obiecują one łatwiejszą komunikację, wersjonowanie modeli/dokumentów, wykrywanie kolizji, ekstra premie za ukończenie projektu przed czasem. A nie, wróć, to budownictwo 😉

GitHub jest właśnie takim CDE, tylko, że darmowym. Oczywiście do pewnego zakresu funkcjonalności, jest też wersja płatna, ale spokojnie, nie dotrzemy do tego etapu. Z GitHub’a korzysta więcej niż 70 milionów programistów, pracując nad szeregiem projektów, w znacznej części publicznych. Robią to zazwyczaj już po godzinach swojej zwykłej pracy.

Dlaczego ktoś chciałby za darmo ‘pracować’ po godzinach? Co programista to inny powód, jednak najczęściej jest to chęć rozwijania się w innym kierunku niż ten aktualny w pracy. Programowanie to szereg wyborów, co człowiek to inne podejście i trudno żeby Twój pomysł zawsze był tym najlepszym. Dopiero konfrontacja z innymi ludźmi, z ich ideami daje możliwość spojrzenia na problem szerzej. W ten sposób rozwijasz swoje umiejętności, zarówno miękkie jak i twarde. Uczysz się współpracować i klepać lepsze, wydajniejsze programy. Poświęcając swój czas – zyskujesz.

Na GitHub znajdziesz wiele przykładów jak tworzyć rozszerzenia do Tekli. Rzuć okiem na repozytorium TSOpenAPIExamples lub na porfolio projektów Dawida Dyrcza..

Dobra, pogadałem, czas na konkrety: tu jest link do naszego repozytorium LetsConstructIT. Tam będą pojawiać się kolejne programy powstałe w ramach społeczności. Na tym etapie wiedz, że gdy okrzepniesz będziesz mógł wprowadzać zmiany do różnych repozytoriów. Obecnie poruszaj się raczej w obszarze read-only, czyli pobieraj kod źródłowy na swój komputer (Code -> Download ZIP).

Ok, pobrałeś repozytorium, rozpakowałeś ZIP’a, uruchomiłeś solucję TeklaApplications.sln w Visual Studio. Być może nawet skompilowałeś projekt SectionAligner. Warto żebyś wysłuchał opowieści o wersjach bibliotek.

Co roku zarówno producenci hardware jak i software wypuszczają nowe wersje swoich flagowców. Znak czasu, przejaw rozwiniętego kapitalizmu ktoś by powiedział. Nowe wersje = pieniądze za aktualizacje, wsparcie, itd. Waluta w gospodarce wędruje, wszyscy (no, prawie) są zadowoleni. Jak to przekłada się na programowanie? Niestety, nie operujemy w próżni, rozszerzenia Tekli opierają się na … Tekli, jej systemie wersji. Co roku, wraz z pojawieniem się nowej instancji publikowana jest kolejna wersja Tekla Open API. Na szczęście, jądro API Tekli jest stałe od wielu wersji. Bardzo rzadko zdarzają się wersje zmieniające funkcjonalność poprzednika. Skorzystamy z tego faktu w celu ułatwienia rozwoju nakładek.

W klasycznym schemacie kompilowanie pod różne Tekle wymaga przepinania wykorzystywanych bibliotek Tekla Open API na adekwatne wersje. Da się to zrealizować przez różne konfiguracje w Visual Studio, można pójść w stronę zaproponowaną przez Dawida Dyrcza, można też inaczej. W C# dostępny jest mechanizm przekierowań wersji wykorzystywanych bibliotek w trakcie uruchamiania programu. Czyli kompilujemy program celując np. w Teklą 2020 a uruchamiamy go pod Teklą 2018i. Lub jeśli jest taka potrzeba pod 2021. Wszystko dzięki elementowi <bindingRedirect> w konfiguracji programu.

Spójrz proszę w zawartość pliku App.config. Obecnie zawiera on wskazanie, że w trakcie uruchamiania programu należy używać wersji 2022.0.0.0 Teklowego API. Jeżeli np. pracujesz w Tekli 2016i to chcesz żeby Twój App.config wykorzystywał Teklowe biblioteki w wersji 2016.1.0.0. Zmień więc u siebie lokalnie zawartośc App.config stosownie do używanej wersji Tekli. Ciekawostką jest fakt, że ten mechanizm jest używany w ramach przygotowywania plików .tsep, czyli instalatorów Teklowych dodatków. Szerszy opis dostępny pod linkiem.

Dostosowałeś lokalnie App.config, możesz skompilować i uruchomić program. Tylko, właściwie jak to działa? Punktem wejścia do programu jest statyczna metoda Main znajdującą się w pliku Program.cs.

internal class Program
{
    static void Main(string[] args)
    {
        Aligner aligner = new Aligner();
        aligner.Execute();
    }
}

Jej jedyną odpowiedzialnością jest wywołanie metody Execute klasy Aligner, która dyryguje resztą procesu. Spójrzmy na tę klasę:

public class Aligner
{
    private DrawingHandler _dh;
    private Drawing _activeDrawing;

    public void Execute()
    {
        if (!InitailizeDrawingHandler())
            return;

        SectionAggregator sectionAggregator = new SectionAggregator(_dh, _activeDrawing);
        sectionAggregator.GetSectionsFromDrawing();
        sectionAggregator.SourceViews.ForEach(x => x.Align());

        _activeDrawing.CommitChanges();
    }

    private bool InitailizeDrawingHandler()
    {
        _dh = new DrawingHandler();
        if (!_dh.GetConnectionStatus())
            return false;

        _activeDrawing = _dh.GetActiveDrawing();
        if (_activeDrawing == null)
            return false;

        return true;
    }
}

Pierwsze co robi metoda Execute to (linijka 8 wywołującą 18-28) sprawdzenie czy znajdujemy się w obszarze rysunku. DrawingHandler to punkt wejścia do komunikacji z Teklowymi rysunkami. Przy jego pomocy możemy otwierać, zapisywać, edytować, drukować poszczególne rysunki. Jeżeli coś idzie nie tak z połączeniem z Teklą (nie jest włączona, wersje bibliotek są niezgodne, uruchomiona jest więcej niż jedna Tekla) to metoda DrawingHandler.GetConnectionStatus() zwróci false oznaczający brak połączenia. Dopasowywanie przekrojów ma sens tylko wtedy, gdy włączony jest rysunek – stąd sprawdzenie w linijka 24-26.

W wariancie pozytywnym: udane połączenie + rysunek aktywny, idziemy dalej, czyli w ramach metody Execute wywoływane są linijki 11-15. Tam napotykamy na wywołanie nowej klasy – SectionAggregator, która pozycjonuje przekroje zgodnie z zadaną logiką. Zachęcam do prześledzenia działania poszczególnych metod. Analizowanie działania algorytmów to podstawa do tworzenia własnych.

W ramach tego wpisu chcę poruszyć dwa różne scenariusze pobierania elementów z Tekli. W zalezności od potrzeb możesz potrzebować wszystkich obiektów danego typu, spełniających określony zestaw cech. Zrobiłem tak w metodzie GetAllSectionViews w ramach klasy SectionAggregator:

private IEnumerable<View> GetAllSectionViews()
{
    foreach (var item in _drawing.GetSheet().GetAllViews())
    {
        if (item is View)
        {
            View view = (View)item;
            if (view.ViewType == View.ViewTypes.SectionView)
                yield return view;
        }
    }
}

W powyższym listingu pobieram najpierw arkusz z danego rysunku. Arkuszem jest najwyższą instancją rysunkową do której wstawiane są kolejne widoki. I to własnie mając arkusz, mogę te widoki pobrać wykorzystując metodę GetAllViews. Następnie sprawdzam typ widoku – logika aplikacji wymaga w tym miejscu pobrania wyłącznie przekrojów, co zostało zapisane w linijce 8-ej.

Drugi schemat pobierania elementów to potrzeba wykorzystania selekcji, którą wykonał już użytkownik. Nie interesuje nas wszystko na rysunku, chcemy wiedzieć co jest obecnie zaznaczone. W tym celu powstała metoda GetOnlySelectedViews bazująca na pobraniu od DrawingHandler’a obiektu klasy DrawingObjectSelector. Dzieje się to w linijce 5-ej poniżej:

private List<SourceViewWithSections> GetOnlySelectedViews()
{
    List<SourceViewWithSections> sourceViews = new List<SourceViewWithSections>();

    foreach (var selectedObject in _dh.GetDrawingObjectSelector().GetSelected())
    {
        if (selectedObject is View)
        {
            View selectedView = selectedObject as View;
            if (selectedView.ViewType == View.ViewTypes.SectionView)
            {
                AddSectionViewWithRelatedSource(selectedView, sourceViews);
            }
            else
            {
                AddSectionViewsFromMainView(selectedView, sourceViews);
            }
        }
    }

    return sourceViews;
}

Wywołując GetSelected() na obiekcie DrawingObjectSelector pobieram aktualne zaznaczenie w obszarze rysunku. Dalej następuje sprawdzenie czy ten obiekt odpowiada naszym potrzebom – czy jest widokiem o typie ‘przekrój’.

Jak widzisz operujemy na bibliotece, którą dostarcza Trimble. Komunikujemy się z rysunkiem (w przyszłości z modelem) w ramach przewidzianych przez producenta programu. Ktoś w centrali dawno temu założył, że potrzebny jest DrawingHandler, który udostępni metodę GetSheet, która zwróci arkusz, z którego będziemy mogli to i owo. Kluczem jest zrozumienie, że ktoś poczynił pewne założenia jak to API ma działać i co ma oferować. I choć możliwości rozszerzania Tekli są znaczne, to mają swój kres – granicą są możliwości Tekla Open API, która jest furtką do bazy danych kryjącej pod schludnym interfejsem programu.

Żeby nie kończyć aż tak pesymistycznie (napotkasz na bariery, których nie skruszysz) chcę pokazać Ci dwie sztuczki, które możesz wykorzystać aby wydobyć więcej z API. Pierwszą z nich są metody rozszerzeniowe, czyli doklejki zwiększające możliwości jakie mają Teklowe obiekty. Spójrz proszę na screen:

Widzimy wywołanie metody GetId() na obiekcie klasy View. Ciekawostką jest fakt, że w oryginale klasa View nie posiada takiej metody. Dodałem ją jako metodą rozszerzeniową dodając nową moc do widoku:

public static class DrawingExtensions
{
    public static int GetId(this DatabaseObject view)
    {
        var identifier = (Tekla.Structures.Identifier)view.GetPropertyValue("Identifier");
        return identifier.ID;
    }
}

Deklarowanie metod rozszerzeniowych jest możliwe tylko w ramach statycznych klas. Sama metoda musi być statyczna a jej argument jest poprzedzony słowem kluczowym this. W ten sposób możesz dodawać nowe umiejętności klasom, które nie są Twoje, które pochodzą z zewnętrznych bibliotek.

W ramach tego rozszerzania wykorzystałem jeszcze jedną funkcjonalność C#. Tekla Open API nie eksponuje publicznie identyfikatorów (unikalnych adresów) obiektów rysunkowych. Ma je jednak wewnątrz swoich klas jako wewnętrzne właściwości. O proszę, tutaj są:

No i klops, przydałby nam sie unikalny identyfikator obiektu rysunkowego a ktoś kiedyś uznał, że nie powinien on być eksponowany jako publiczny. Na pewno miał ku temu powód (pewnie brak zaufania co do jego trwałości), ale my go potrzebujemy dosłownie na chwilkę – nie będziemy go zapisywać na przyszłość i wykorzystywać za tydzień. Jest on nam potrzebny w ramach tej sesji pracy w programie, więc hello – dajcie nam go!

Niestety, nikt nam go nie da – musimy pójśc po niego sami i właśnie do tego potrzebujemy tytułowej refleksji, którą wykorzystałem w ramach statycznej klasy ReflectionHelper. Dzięki niej jesteśmy w stanie w trakcie działania programu wydobyć to co ukryte – poznać wewnętrzny identyfikator obiektu rysunkowego.

Na ten moment wystarczy. Refleksja, metody roszerzeniowe, wspołpraca w ramach GitHub – pojawiło się kilka wartościowych nowości. W kolejnym wpisie przedstawię różne możliwości dystrybuowania Twoich Teklowych roszerzeń. Do usłyszenia!

Series Navigation

Leave a Reply

Your email address will not be published. Required fields are marked *