AutoCAD, AutoLISP, CADPL-Pack

Gdzie jest TRASA?

Dzisiejszy wpis poświęcony jest niedostępnemu już w AutoCAD-zie poleceniu TRASA. Przykładowe proste makra pokazują też, że nie wszystko stracone, i że można nadal tworzyć obiekty TRACE. Proces usunięcia polecenia TRASA (_TRACE) z AutoCAD-a został zapowiedziany już w wersji 2009, a ostateczne pożegnanie nastąpiło w wersji 2012. Za pomocą tego polecenia można było tworzyć wypełnione odcinki o zadanej szerokości (którą określała zmienna systemowa TRACEWID). Trzeba przyznać że było to stosunkowo prymitywne narzędzie. Segmenty skonstruowane były jako wypełnione czworokąty (osobne obiekty typu TRACE). Segmenty nie były ze sobą powiązane, a edycja uchwytami dotyczyła osobno każdego z wierzchołków. Z tych powodów trasy były rzadko używane – dużo wygodniejsze w tworzeniu i edycji są polilinie. Być może wielu użytkowników AutoCAD-a, mogło nie zauważyć nawet faktu usunięcia możliwości rysowania tras.

Oczywiście w celu zachowania zgodności obiekty typu TRACE nadal istnieją w AutoCAD-zie i w specyfikacji DXF, jednak jedynymi możliwościami ich tworzenia, po wycofaniu polecenia TRACE, są mechanizmy API (LISP / VBA / ActiveX / NET).

Ciekawą informacją jest fakt istnienia drugiego obiektu bliźniaczo podobnego do wycofanego TRACE. Tym obiektem jest SOLID. Oba zresztą należą do tej samej  klasy AcDbEntity / AcDbTrace. Poniżej porównanie list DXF tych dwóch obiektów:

Jak widać obiekty nie różnią się niczym oprócz nazwy obiektu (kod DXF = 0), i (co oczywiste uchwytu (handle – kod DXF 5). Na marginesie – polecenie o nazwie _SOLID, w wersji polskiej ustalone jako OBSZAR, zaś sam obiekt przetłumaczony jako BRYŁA. Wydaje się że obie zlokalizowane nazwy nie są w tym przypadku dość szczęśliwe.

Jako przykład ilustrujący tworzenie obiektu TRACE, będącego wypełnionym prostokątem, przedstawiam poniżej dwie funkcje (zdefiniowane polecenia: RETA i RETE). Dodatkowo mają one możliwość rysowania SOLID zamiast TRACE. Polecenie RETA, wykorzystuje mechanizmy ActiveX. Poniżej kod:


; =============================================================== ;
; C:RETA - Tworzy wypełniony prostokat (TRACE / SOLD) metodami    ;
;          ActiveX - kojacek - 2017 -                             ;
; =============================================================== ;
(defun C:RETA (/ p c r o e)
  (if (not *jk-ReTrace*)(setq *jk-ReTrace* "Trasa"))
  (if
    (setq r
      (cd:USR_GetKeyWord
        "\nRysuj wypełniony prostokąt"
        '("Obszar" "Trasa") *jk-ReTrace*
      )
    )
    (if
      (setq p (getpoint "\nOkreśl pierwszy narożnik: "))
      (if
        (setq c
          (cd:USR_GetCorner p "\nOkreśl drugi narożnik: " T)
        )
        (progn
          (setq e (getvar "ELEVATION")
                c (list (car c)(cadddr c)(cadr c)(caddr c))
                c (mapcar '(lambda (%)(append % (list e))) c)
                c
                   (if
                    (= r "Trasa")
                    (apply 'append c)
                    (mapcar 'vlax-3d-point c)
                  )
                *jk-ReTrace* r
          )
          (cd:SYS_UndoBegin)
          (if
            (= *jk-ReTrace* "Trasa")
            (vla-AddTrace (cd:ACX_ASpace)
              (vlax-make-variant
                (vlax-safearray-fill
                  (vlax-make-safearray vlax-vbdouble
                    (cons 0 (1- (length c)))
                  )
                  c
                )
              )
            )
            (vla-AddSolid (cd:ACX_ASpace)
              (car c)(cadr c)(caddr c)(cadddr c)
            )
          )
          (cd:SYS_UndoEnd)
        )
        (princ "\nBłąd. Nie wskazano prawidłowego punktu. ")
      )
      (princ "\nBłąd. Nie wskazano prawidłowego punktu. ")
    )
    (princ "\nBłąd. Nie wybrano opcji. ")
  )
  (princ)
)

Polecenie RETE, którego definicja jest przedstawiona poniżej, funkcjonalnie wykonuje to samo działanie co polecenie RETA, z uwagi jednak na zastosowanie funkcji entmakex, kod makra jest bardziej zoptymalizowany. Lista danych DXF jest w obu przypadkach taka sama, z wyjątkiem danych kodu 0 (TRACE / SOLID). Poniżej kod:


; =============================================================== ;
; C:RETE - Tworzy wypełniony prostokat (TRACE / SOLD) funkcja     ;
;          entmakex - kojacek - 2017 -                            ;
; =============================================================== ;
(defun C:RETE (/ p c r o e)
  (if (not *jk-ReTrace*)(setq *jk-ReTrace* "Trasa"))
  (if
    (setq r
      (cd:USR_GetKeyWord
        "\nRysuj wypełniony prostokąt"
        '("Obszar" "Trasa") *jk-ReTrace*
      )
    )
    (if
      (setq p (getpoint "\nOkreśl pierwszy narożnik: "))
      (if
        (setq c
          (cd:USR_GetCorner p "\nOkreśl drugi narożnik: " T)
        )
        (progn
          (setq e (getvar "ELEVATION")
                c (list (car c)(cadddr c)(cadr c)(caddr c))
                c (mapcar '(lambda (%)(append % (list e))) c)
                c (list
                    (cons 10 (car c))
                    (cons 11 (cadr c))
                    (cons 12 (caddr c))
                    (cons 13 (cadddr c))
                  )
                *jk-ReTrace* r
          )
          (cd:SYS_UndoBegin)
          (entmakex
            (append
              (list
                (cons 0
                  (if (= *jk-ReTrace* "Trasa") "TRACE" "SOLID")
                )
                (cons 100 "AcDbTrace")
              ) c
            )
          )
          (cd:SYS_UndoEnd)
        )
        (princ "\nBłąd. Nie wskazano prawidłowego punktu. ")
      )
      (princ "\nBłąd. Nie wskazano prawidłowego punktu. ")
    )
    (princ "\nBłąd. Nie wybrano opcji. ")
  )
  (princ)
)

Oba polecenia będą działać poprawnie po załadowaniu biblioteki CADPL-Pack, w wersji z aktualizacją kwietniową 2017. Została tam dodana wykorzystywana tutaj funkcja, wskazania punktów o nazwie cd:USR_GetCorner:

( . . . )

AutoLISP

AutoLISP i pliki *.ini

Dzisiaj parę słów o plikach *.ini, i możliwościach manipulowania nimi za pomocą AutoLISP-a. Pliki o rozszerzeniu ini, to zwykłe pliki tekstowe, zawierające dane sformatowane w uporządkowany sposób. Wykorzystywane są jako pliki inicjalizacyjne i konfiguracyjne, w wielu programach systemu Windows. To stosunkowo przestarzały sposób, niemniej nadal szeroko wykorzystywany, ze względu na swoją prostotę, przejrzystość i elastyczność. Możliwość edycji pliku zarówno przez specjalizowany program, jak również przez użytkownika (w zwyczajnym edytorze tekstowym), uważana jest za zaletę. Głównymi wadami przechowywania danych w ten sposób są: ograniczenie typów danych (to tylko plik tekstowy), brak możliwości zagnieżdżenia danych i (co wyżej wymieniłem jako zaletę) – zbyt łatwy dostęp. Struktura pliku ini jest następująca:

  • dane pogrupowane są w unikalne sekcje, których nazwa umieszczona jest pomiędzy kwadratowymi nawiasami.
  • w każdej sekcji w kolejnych liniach dane są sformatowane jako asocjacje rozpoznawane przez klucz (unikalny w sekcji), po którym występuje separator (znak równości), a następnie właściwa wartość.
  • znak średnika na początku linii oznacza komentarz i jego treść jest ignorowana (tak jak w LISP-ie)

Poniżej przykład zawartości pliku has.ini, który z powodzeniem stosuję do zapisywania ustawień programu has.lsp:

Krótki program has.lsp, wykorzystujący ustawienia właśnie w pliku ini,postaram się przedstawić w bliskiej przyszłości, teraz omówię sposoby manipulacji danymi przechowywanymi w plikach *.ini, za pomocą AutoLISP-a, z których on korzysta.

Ani sam AutoLISP, ani VisualLISP nie oferują żadnych bezpośrednich funkcji dostępu do plików typu ini. Oczywiście można napisać własne funkcje tego typu, bazując na mechanizmach zapisu i odczytu plików tekstowych, jakie oferuje LISP. Istnieją jednak jeszcze dwie drogi: wykorzystanie biblioteki acetutil.arx, z pakietu ExpressTools, lub narzędzia firm trzecich – na przykład DOSLib (funkcje dos_getini i dos_getini). W swojej praktyce z reguły korzystam z tej pierwszej możliwości – zawsze bowiem, w każdej wersji AutoCAD-a instaluję ET.

W acetutil.arx zostały zdefiniowane dwie funkcje dostępu do plików *.ini. Są to:

  • acet-ini-get – służąca do odczytu. Jej składnia wygląda tak:
    (acet-ini-get inifile [section [key [default]]])

    gdzie wymaganym argumentem jest inifile – nazwa pliku do odczytu, oraz opcjonalne argumenty section, key i default. Jeśli występuje tylko argument inifile, ta funkcja zwraca listę nazw sekcji lub nil w przypadku gdy nie można otworzyć pliku ini do odczytu. Jeżeli występuje argument z nazwą sekcji (section), zostanie zwrócona lista nazw kluczy w tej sekcji. Jeżeli argumentem jest key, zwracana jest wartość zeń skojarzona. Argument default (jeśli występuje) zwracany jest w przypadku gdy nie można znaleźć wartości podanej w argumencie key. Funkcja zwraca nil, jeśli nie można znaleźć żadnych informacji.

  • acet-ini-set – służąca do zapisu. Jej składnia wygląda tak:
    (acet-ini-set inifile section [key [value]])

    gdzie dwa wymagane argumenty to oczywiście  inifile – nazwa pliku, oraz section. Kolejne (key i value) są argumentami opcjonalnymi. Jeżeli zostanie podany argument section z nieistniejącym wpisem w pliku (i równocześnie podane zostaną argumenty key i value), zostanie on utworzony, wraz z kluczem i przypisaną wartością . Wywołanie funkcji tylko z argumentem section (istniejącym w pliku), zostanie wtedy usunięta cała sekcja (łącznie ze wszystkimi wpisami w sekcji). Argument value zawsze nadpisuje znalezioną w pliku wartość klucza key.

Stosując tylko te dwie funkcje mamy możliwość pełnej kontroli nad ustawieniami zapisywanymi i odczytywanymi w dowolnym pliku ini. Oczywiście pamiętać należy o ograniczeniach, także wcześniej wspomnianych. Ponadto należy zwracać uwagę na używanie w nazwach czy wartościach małych i wielkich liter.

Alternatywnym sposobem zapisywania ustawień programów jest wykorzystywanie dostępu do rejestru Windows. Niemniej jednak stosowanie plików ini, w wielu przypadkach jest ze względu na swoją prostotę i elastyczność rozwiązaniem najbardziej optymalnym.

(  . . . )

AutoCAD, AutoLISP

Łuk: punkt początkowy i końcowy

Na jednym z forów cad-owskich, pojawiło się pytanie czy można w AutoCAD-zie, za pomocą polecenia WYODRDANYCH (_DATAEXTRACTION), otrzymać informacje o początkowym i końcowym punkcie łuku. Niestety nie jest to możliwe. Dane geometryczne obiektów typu ARC, jakie „widzi” kreator wyodrębniania danych, pokrywają się z tymi jakie wystarczają do konstrukcji dowolnego łuku. AutoLISP oferuje tutaj znacznie więcej.

Wydaje się że pojawiła się okazja aby przypatrzeć się bardziej szczegółowo łukom:

W rysunkowej bazie danych rysunku, które AutoLISP przedstawia jako listy danych DXF, znajdują się tylko konieczne do opisu geometrii łuku dane. Są to: punkt środkowy (kod DXF=10), promień (DXF=40), oraz kąty początkowy (DXF=50) i końcowy (DXF=51). Ten minimalistyczny opis geometrii obiektów powoduje że wszystkie inne dane (w tym właśnie na przykład punkty początku i końca łuku), trzeba obliczyć.

Dla punktów początkowego i startowego, za pomocą AutoLISP-a, do obliczeń można wykorzystać funkcję polar:

(polar CenterPoint Start/EndAngle Radius)

W modelu ActiveX danych dotyczących geometrii łuku jest znacznie więcej. Dodatkowo znajdziemy tutaj: długość (ArcLenght), powierzchnię (Area), punkt końcowy (EndPoint), punkt początkowy (StartPoint), oraz miarę kąta łuku (TotalAngle). Podobne dane uzyskamy poprzez dostęp do właściwości VisualLISP (wersje AutoCAD-a od 2012). Dane odczytać można za pomocą funkcji dumpallproperties (także: getpropertyvalue / setpropertyvalue). O dumpallproperties pisałem już TUTAJ. Oprócz powyższych właściwości, dodatkowo widać charakterystyczne dla krzywych cechy: StartParam i EndParam.

Poniżej zestawienie podstawowych cech łuku z przykładowymi wywołaniami LISP-a, w linii poleceń AutoCAD-a. W nawiasie podałem typy zwracanych danych.

Ponadto, warto wspomnieć że dla obiektu ARC, mają również zastosowanie funkcje VisualLISP-a, typu vlax-curve-... ,  na przykład vlax-curve-getEndPoint, vlax-curve-getStartPoint, czy vlax-curve-getArea.

(  . . . )

AutoLISP

LAYER – nieudokumentowana własność – isHidden

O warstwach i ich cechach już parę razy wspominałem w tych wpisach. Teraz parę słów o pewnej nieudokumentowanej własności obiektu LAYER. To cecha o nazwie isHidden. Właściwość isHidden odpowiada za widoczność nazwy warstwy w oknach, listach rozwijalnych itp. Przyjmuje dwie wartości 0 lub 1. Poniżej przykład ilustrujący praktyczne działanie zmiany właściwości. Narysujmy dwa obiekty – nich to będzie linia i okrąg:

Niechże czerwony okrąg (z kolorem jakwarstwa) będzie na warstwie o nazwie B…

… zaś żółta linia (także jakwarstwa), niech leży na warstwie A:

Jak widać w rysunku istnieją tylko trzy warstwy: 0, A oraz B. Za pomocą LISP-a zdefiniowałem polecenie HL, które nieco zmienia ten obraz:

Polecenie wymaga wskazania dowolnego obiektu – tutaj wybieramy okrąg:

… i właściwie nic się nie dzieje dopóty, dopóki nie rozwiniemy listy warstw. Na liście widoczne są dwie warstwy: 0 i A. Pamiętamy że okrąg (nadal istniejący) znajduje się na warstwie B.

Zaznaczenie obiektu na „nieistniejącej warstwie” powoduje „wyczyszczenie” pola listy warstw:

Poniżej przedstawiam krótkie makro – definicję polecenia HL, które dla wskazanego obiektu, ukrywa jego warstwę, zmieniając cechę isHidden. Dla poprawności działania, wykluczyłem możliwość ukrywania warstw 0 i DefPoints, jak również warstwy aktualnej. Polecenie działa jak przełącznik sekwencyjny – ukrywa / odkrywa warstwę, w zależności od jej stanu w chwili wskazania obiektu. Cały kod poniżej:


; =============================================================== ;
; hiddenlay.lsp - kojacek - 2017 -                                ;
; =============================================================== ;
(defun C:HL (/ e d l f)
  (if
    (and
      (setq e (car (entsel "\nWybierz obiekt: ")))
      (not
        (member
          (setq l
          (cdr (assoc 8 (setq d (entget e)))))
         '("0" "DEFPOINTS")
        )
      )
    )
    (if
      (/= (getvar "CLAYER") l)
      (progn
        (setq f (if (= (substr l 1 1) "*") 0 1))
        (setpropertyvalue (tblobjname "LAYER" l) "IsHidden" f)
        (princ
          (strcat
            "\nWarstwa "
            (if (zerop f)(substr l 2 (strlen l)) l)
            " jest " (nth f '("od" "u")) "kryta. "
          )
        )
      )
      (princ "\nObiekt na warstwie aktualnej. ")
    )
  )
  (princ)
)
; =============================================================== ;
(princ)

Warto zwrócić uwagę że właściwość isHidden nie ma bezpośredniego odzwierciedlenia w kodach DXF danych obiektu. Jedyna różnica to nazwa warstwy poprzedzona znakiem gwiazdki. I jest to jedyny sposób aby z poziomu AutoLISP-a, odczytać czy warstwa jest „ukryta”:

Tak samo jest dla danych odczytanych dla VLA-OBJECT. Tylko gwiazdka w nazwie warstwy świadczy o różnych wartościach własności isHidden:

Fakt możliwości zamiany nazwy warstwy, poprzez zmianę właściwości isHidden, nie jest nigdzie udokumentowany, ani w DXF Reference, ani podręcznikach programisty traktujących o AutoLISP / VisualLISP czy ActiveX dla AutoCAD-a.  IsHidden widoczne jest tylko dla danych uzyskanych za pomocą funkcji dumpallproperties, i tylko przy pomocy funkcji getpropertyvalue i setpropertyvalue , możliwy jest jej odczyt i zmiana. Poniższe okno odczytu danych opisałem w Visual dumpallproperties.

(  . . . )
 

AutoLISP

Podział zamkniętej polilinii

Dzisiejszy wpis traktuje o podziale zamkniętej polilinii wzdłuż prostej wyznaczonej przez dwa wskazane punkty, w efekcie czego powstają dwie nowe zamknięte polilinie. Oczywiście o procesie zautomatyzowania tej czynności za pomocą makra AutoLISP. Wybrana polilinia może składać się z odcinków prostoliniowych i łukowych, a pierwszymi warunkami jej prawidłowego wyboru są: zamknięcie i powierzchnia większa od zera (eliminacja zamkniętej dwusegmentowej polilinii):

Wskazanie dwóch punktów, wyznaczających „cięcie” – muszą to być punkty, wskazane „na zewnątrz” obiektu, lub na nim leżące:

Kolejny warunek poprawności wyboru, wymaga aby prosta wyznaczona przez wskazane punkty, (nawet na pozornym przedłużeniu), miała tylko dwa punkty przecięcia ze wskazaną polilinią. Po spełnieniu tych warunków, w miejscu jednej polilinii, tworzone są dwa nowe obiekty.

Do utworzenia nowych obwiedni wykorzystuję tutaj funkcję bpoly, które dla poprawnego obszaru ograniczonego, tworzy automatycznie polilinię. Dla sprawdzenia poprawności utworzenia nowych obiektów, porównuję powierzchnie nowopowstałych obiektów z powierzchnią polilinii źródłowej. Ma to na celu uniknięcie zwykle nieudanej próby podziału polilinii samoprzecinającej się, lub gdy wskazaną polilinię przecinają także inne obiekty. Poniżej cały kod makra, definicji polecenia SECPOLY. Nowopowstałe obiekty przyjmują podstawowe cechy (kolor / warstwa / rodzaj linii) z polilinii źródłowej. Do działania konieczne jest załadowanie CADPL-Pack’a:


; =============================================================== ;
; secpoly.lsp - kojacek - 2017 -                                  ;
; =============================================================== ;
; Polecenie SECPOLY dzieli zamknieta polilinie na dwie odrebne    ;
; zamkniete polilinie, wzdluz linii wyznaczonej przez 2 wskazane  ;
; punkty. Linia wyznaczajaca ciecie musi przebiegac tylko przez   ;
; dwa segmenty polilinii.                                         ;
; --------------------------------------------------------------- ;
(defun C:SECPOLY
  (/ *error* :z :i s d e o p1 p2 l p q a n1 n2 u x s1 s2 v)
  (defun *error* (msg) 
    (or
      (wcmatch (strcase msg) "*BREAK,*CANCEL*,*EXIT*")
      (progn
        (cd:SYS_UndoEnd)
        (if l (vla-delete l))
        (foreach % (list s1 s2)(if % (entdel %)))
        (princ (strcat "\n*** Błąd: " msg " ***"))
      )
    )
    (princ)
  )
  (defun :z (e / a b)
    (if e
      (progn
        (vla-GetBoundingBox (vlax-ename->vla-object e) 'a 'b)
        (vla-ZoomWindow (vlax-get-acad-object) a b)
      )
      (vla-ZoomPrevious (vlax-get-acad-object))
    )
  )
  (defun :i (v1 v2 Tp / x r %)
    (if
      (vl-catch-all-error-p
        (setq x
          (vl-catch-all-apply
            'vlax-safearray->list
            (list
              (vl-catch-all-apply
                'vlax-variant-value
                (list (vla-intersectwith v1 v2 Tp))
              )
            )
          )
        )
      )
      (setq r nil)
      (repeat
        (/ (length x) 3)
        (setq % (list (car x)(cadr x)(caddr x)))
        (setq r (cons % r))
        (setq x (cdddr x))
      )
    ) r
  )
  (if
    (setq s (entsel "\nWybierz polilinię do podziału: "))
    (if
      (and
        (= (cdr (assoc 0 (setq d (entget (setq e (car s))))))
           "LWPOLYLINE"
        )
        (not (zerop (getpropertyvalue e "Area")))
        (= 1 (logand 1 (cdr (assoc 70 d))))
      )
      (if
        (and
          (setq p1 (getpoint "\nWskaż pierwszy punkt cięcia: "))
          (setq p2 (getpoint p1 "\nWskaż drugi punkt cięcia: "))
        )
        (progn
          (cd:SYS_UndoBegin)
          (setq l (cd:ACX_AddLine (cd:ACX_ASpace) p1 p2 T)
                o (vlax-ename->vla-object e)
                v (cd:ACX_GetProp o
                    '("Color" "Layer" "LineType" "LineTypeScale")
                  )
          )
          (if
            (and
              (setq p (:i o l AcExtendNone))
              (setq q (:i o l acExtendOtherEntity))
              (= (length p)(length q) 2)
            )
            (progn
              (setq n1 (car p)
                    n2 (cadr p)
                    i (distance n1 n2)
                    a (angle n1 n2)
                    x (polar n1 a (* 0.5 i))
                    u (* i 0.01)
              )
              (:z e)
              (bpoly (polar x (+ a (* 0.5 pi)) u))
              (setq s1 (entlast))
              (bpoly (polar x (+ a (* 1.5 pi)) u))
              (setq s2 (entlast))
              (:z nil)
              (if
                (equal
                  (getpropertyvalue e "Area")
                  (+
                    (getpropertyvalue s1 "Area")
                    (getpropertyvalue s2 "Area")
                  )
                  0.001
                )
                (progn
                  (cd:ACX_SetProp s1 v)
                  (cd:ACX_SetProp s2 v)
                  (vla-delete l)(entdel e)
                )
                (progn
                  (foreach % (list s1 s2)
                    (if % (entdel %))
                  )
                  (if l (vla-delete l))
                  (princ "\nNie można utworzyć podziału polilinii. ")
                )
              )
            )
            (progn
              (vla-delete l)
              (princ "\nNie można utworzyć podziału polilinii. ")
            )
          )
          (cd:SYS_UndoEnd)
        )
        (princ "\nNie wskazano punktu. ")
      )
      (princ "\nNiepoprawny wybór. ")
    )
    (princ "\nNic nie wybrano. ")
  )
  (princ)
)
; =============================================================== ;
(princ)

Poniższy obraz ilustruje niepoprawne wybory krawędzi (w pierwszy rzędzie), oraz (w drugim), poprawne. Choć przecinana polilinia może być figurą wklęsłą, konieczne jest jednak wskazanie linii cięcia przechodzącej tylko przez dwa punkty polilinii:

W zdecydowanej większości przypadków, polecenie SECPOLY, działa poprawnie, choć zauważyłem rzadko zdarzające się błędy przy skomplikowanych poliliniach z nakładającymi się na siebie segmentami.

(  . . . )

AutoLISP, CADPL-Pack

Zbiór wskazań zaznaczony polilinią

Tworzenie zbioru wskazań „po ścieżce” utworzonej przez wybór polilinii wydaje się dość prostą sprawą. Wykorzystać można do tego tryb wyboru krawędzią funkcji ssget – „_F” (Fence). Pamiętać jednak należy o kilku rzeczach:

  • polilinia nie powinna mieć segmentów łukowych – warunek nie jest konieczny, ale wiedzieć trzeba że zostaną wybrane tylko obiekty które leżą na segmentach liniowych. Jeśli zachodzi taka potrzeba, łuki polilinii muszą być aproksymowane liniami. W dzisiejszym wpisie omówię tylko wybór segmentami liniowymi.
  • polilinia musi być w całości widoczna na ekranie – ssget wybiera tylko widocznymi krawędziami. Prawidłowy zbiór wskazań uzależniony jest od spełnienia tego warunku.
  • od wersji AutoCAD 2004, kolejność obiektów w zbiorze wskazań utworzonym przez _fence, jest zgodna z kierunkiem wskazywania wierzchołków krawędzi wyboru. Jeśli kolejność elementów w zbiorze wskazań ma mieć znaczenie, można wykorzystać  tę właściwość, tworząc listę punktów wyboru z wierzchołków polilinii i odwracając ją w razie konieczności. Tutaj wykorzystam wybór segmentu od określenia kierunku wyboru.

Poniżej prezentowana funkcja służy do utworzenia zbioru wskazań przez wskazaną (lekką) polilinię. Ma dwa argumenty: łańcuch tekstowy zgłoszenia, oraz listę warunków zawężających wybór, (jak dla standardowej funkcji ssget). Jeżeli ma wartość nil, wybierane są wszystkie obiekty które są przecinane przez polilinię.


; =============================================================== ;
; funkcja wyboru obiektow przez LWPolyline                        ;
;  msg  - zgloszenie                                              ;
;  Vlst - lista dla ssget                                         ;
; =============================================================== ;
(defun  jk:SSX_SelbyPoly (Msg VLst / s e d i l x :z)
  (defun :z (e / a b)
    (if e
      (progn
        (vla-GetBoundingBox (vlax-ename->vla-object e) 'a 'b)
        (vla-ZoomWindow (vlax-get-acad-object) a b)
      )
      (vla-ZoomPrevious (vlax-get-acad-object))
    )
  )
  (if
    (and
      (setq s (entsel Msg))
      (= (cdr (assoc 0 (setq d (entget (setq e (car s))))))
         "LWPOLYLINE"
      )
    )
    (progn
      (setq i (jk:ENT_GetLWPolySeg e (cadr s))
            l (cd:DXF_Massoc 10 d)
            l (if (= 1 (logand 1 (cdr (assoc 70 d))))
                (append l (list (car l))) l)
            l (if (< i (/ (cdr (assoc 90 d)) 2)) l (reverse l))
      )
      (:z e)
      (if
        (setq x
          (if VLst
            (ssget "_F" l VLst)
            (ssget "_F" l)
          )
        )
        (progn
          (:z nil)
          (setq x (if (ssmemb e x)(ssdel e x) x))
          (if (zerop (sslength x)) nil x)
        )
        (:z nil)
      )
    )
  )
)
; =============================================================== ;
; zwraca numer segmentu obiektu LWPOLYLINE                        ;
;   en - ename lub vla-object LWPOLYLINE                          ;
;   pt - punkt wskazania                                          ;
; =============================================================== ;
(defun jk:ENT_GetLWPolySeg (en pt)
  (fix
    (vlax-curve-getParamAtPoint
      en
      (vlax-curve-getClosestPointTo en pt)
    )
  )
)
; =============================================================== ;

W definicji funkcji głównej zdefiniowana jest funkcja o nazwie :z, która w chwili tworzenia zbioru wskazań, powoduje tymczasowe wyświetlenie wybranej polilinii na całym ekranie, po czym powraca do poprzedniego widoku. Ponieważ korzysta z szybkiego mechanizmu ActiveX, jej działanie jest niezauważalne. Do prawidłowego działania funkcji jk:SSX_SelbyPoly, konieczne są także jeszcze dwie funkcje: jk:ENT_GetLWPolySeg – zwracająca numer segmentu wybranej polilinii na podstawie punktu wskazania, oraz cd:DXF_Massoc z CADPL-Pack‚a, do „wyłuskania” punktów wierzchołków z danych polilinii. Na końcu, zbiór wskazań „oczyszczany” jest z polilinii wybierającej, jeśli znalazła się w wyborze.

Czas teraz na praktyczny przykład. Niechże będzie (jak na poniższym obrazie) polilinia, na której umieszczone są bloki z atrybutem (wszystkie z wartością „0”). Zadaniem niech będzie ponumerowanie, wszystkich obiektów w zależności od miejsca wskazania (bliżej początku lub bliżej końca) polilinii.

Poniżej krótka funkcja testująca – zdefiniowane polecenie TESTSEL, które wymaga wskazania polilinii, i wybiera tylko bloki o nazwie „Nr”, na niej leżące. Dla prawidłowego zbioru wskazań, tworzy na jego podstawie listę obiektów i numeruje je wypełniając wartości atrybutu każdego z nich, począwszy od „1”.  Wykorzystuje do tego funkcje z CADPL-Pack’a: cd:SSX_Convert i cd:BLK_SetAttValueVLA.


(defun C:TESTSEL (/ ss l i)
  (if
    (setq ss (jk:SSX_SelbyPoly "\nWybierz początek: "
               '((0 . "INSERT")(2 . "Nr")))
    )
    (progn
      (setq i 1
            l (cd:SSX_Convert ss 1)
      )
      (foreach % l
        (cd:BLK_SetAttValueVLA % "No" (itoa i))
        (setq i (1+ i))
      )
    )
  )
  (princ)
)

Poniższy obraz ilustruje działanie polecenia TESTSEL, przy wskazaniu polilinii na jej początku (choć nie znamy kierunku polilinii):

… oraz takie samo działanie dla wyboru polilinii w punkcie leżącym na trzecim od końca segmencie:

W ułamku sekundy wszystkie bloki „wybrane przez polilinię” zostają przenumerowane, w zależności od punktu wyboru.

Prezentowany przykład w stosunkowo prosty sposób ilustruje mechanizm, wykorzystany do tworzenia zbioru wskazań na podstawie punktów wierzchołków wybranej polilinii. W zależności od potrzeb, można napisać bardziej skomplikowane funkcje zbiorów wskazań, niemniej sam sposób w ogólności będzie wyglądał tak samo.

AutoLISP, CADPL-Pack

LISP – Reaktory

Muszę przyznać (nieco ze zdziwieniem nawet) że pomimo już ponad osiemdziesięciu wpisów tutaj, nie pisałem właściwie jeszcze nic o reaktorach… Śpieszę zatem niniejszym odrobić zaległości. Z uwagi na obszerność zagadnień w jednym wpisie nie da się opisać wszystkiego, myślę że jeszcze nie raz będzie okazja napisać parę słów o reaktorach. A że pierwszy krok trzeba zawsze zrobić… Poniżej parę słów o pewnym reaktorze.

Na początek – co to jest reaktor? W największym skrócie reaktor jest obiektem informującym program AutoLISP o wystąpieniu określonego zdarzenia. Reaktor może być dołączony do dowolnego obiektu rysunkowego AutoCAD-a, lub do edytora rysunku. Zdarzeniem zaś może być dowolna zmiana rysunkowej bazy danych (narysowanie entycji, jej usunięcie, zmiana cech obiektów), zmiana stanu zmiennej systemowej, także wywołanie (i / lub zakończenie) polecenia AutoCAD-a, jak również stan urządzenia wskazującego – myszki. Wystąpienie zdarzenia uruchamia odpowiednio zdefiniowaną funkcję odpowiadającą (callback function), czyli reakcję na zdarzenie. Znów – w skrócie: określone zdarzenie, uruchamia krótki program, wykonujący jakieś zadania. Reaktory mogą być tymczasowe (transient – działający podczas sesji z rysunkiem) lub stałe (persistent – zapisane w rysunku, wtedy dla działania wymagają wczytania funkcji odpowiadającej). Szerzej o typach reaktorów, ich właściwościach i możliwościach trzeba będzie napisać w przyszłości, teraz od razu praktyczny przykład reaktora.

Prezentowany poniżej reaktor jest reaktorem (ogólnie) edytora (szczegółowo zaś) informującym aplikację o uruchomieniu polecenia AutoCAD-a (command-reactor), tutaj o wywołaniu dowolnego polecenia wymiarującego.

dh-on

Reaktor jest reaktorem tymczasowym, wymaga wczytania aplikacji (kilku prezentowanych poniżej funkcji), ponadto – musi zostać aktywowany i (może być) dezaktywowany. Służy do tego zdefiniowane polecenie DH. Jego sekwencyjne wywołanie powoduje włączenie / wyłączenie reaktora w zależności od potrzeb.

dh-offW trybie aktywności reaktora, jego działanie polega na ukryciu wszystkich kreskowań (obiektów typu HATCH) w aktywnym oknie AutoCAD-a, w momencie wywołania dowolnego polecenia służącego do tworzenia wymiarów.

dh-dim

Pierwszą wersję tego reaktora napisałem w… 2002 roku, powstał z potrzeby, bowiem wtedy jeszcze możliwe było, przy włączonych trybach lokalizacji, przypadkowe zaczepienie za końce linii wzoru kreskowania. Podczas wymiarowania zaczepienie punktu wymiaru w niewłaściwym miejscu, przy widocznych kreskowaniach, dość często powodowało błędy. Stąd pomysł na wykorzystanie reaktora do tymczasowego ukrycia kreskowania podczas wywołania poleceń wymiarowania, jako alternatywa do „zwykłego” ukrywania warstw z kreskowaniami. Obecnie (i już od dawna), gdy linie kreskowania są „przeźroczyste” dla trybów lokalizacji, funkcjonalność reaktora nieco spadła, niemniej czasem z niego korzystam, bowiem „rozjaśnia” model w trakcie wymiarowania.

Ostatnio nieco go odświeżyłem – najważniejsze zmiany to: wprowadzenia DH jako przełącznika sekwencyjnego, przedtem trzeba było potwierdzać zmianę, wykorzystanie funkcji LM:viewportextents autorstwa Lee Mac‚a, w miejsce mojej podobnej, ale nieco gorzej działającej. Ponadto reaktor teraz także działa na wywołanie polecenia _QDIM (szybkie wymiarowanie – wtedy jeszcze to polecenie nie istniało). Na koniec – kilka zmian kosmetycznych i porządkowych w kodzie, nazwach zmiennych itp.

Cały kod dostępny jest poniżej.  Po załadowaniu, dostępne polecenie DH.


; ===================================================================== ;
; DimHatch - kojacek (2002 / 2017)
; Polecenie DH sekwencyjnie wlacza i wylacza reaktor polecenien AutoCAD-a
; polegajacy na ukrywaniu obiektow typu HATCH po uruchomieniu i w trakcie
; trwania dowolnego polecenia wymiarowania
; ===================================================================== ;

; --------------------------------------------------------------------- ;
; przelacznik wlaczenia reaktora
(defun C:DH (/ m)
  (setq m "\nUkrywanie kreskowania podczas wymiarowania ")
  (if *jk-DH-Status*
    (progn
      (jk:DHR_InitReactor nil)(princ (strcat m "wyłączone. "))
    )
    (progn
      (jk:DHR_InitReactor T)(princ (strcat m "włączone. "))
    )
  )
  (princ)
)
; --------------------------------------------------------------------- ;
; inicjalizacja reaktorow
(defun jk:DHR_InitReactor (Mode)
  (if Mode
    (progn
      (if
        (not *jk-DH-CommReactor*)
        (setq *jk-DH-CommReactor*
          (vlr-Command-Reactor nil
            '( (:vlr-commandWillStart . jk:DHR_CommWillStart)
               (:vlr-commandEnded . jk:DHR_CommEnded)
               (:vlr-commandCancelled . jk:DHR_CommEnded)
               (:vlr-commandFailed . jk:DHR_CommEnded)
             )
          )
              *jk-DH-Status* T
        )
      )
      (if
        (not *jk-DH-DrawReactor*)
        (setq *jk-DH-DrawReactor*
          (vlr-dwg-Reactor nil
           '( (:vlr-beginClose . jk:DHR_CleanReact))
          )
        )
      )
    )
    (setq *jk-DH-CommReactor* nil
          *jk-DH-DrawReactor* nil
          *jk-DH-HatchList* nil
          *jk-DH-Status* nil
    )
  )
)
; --------------------------------------------------------------------- ;
; Reaktor polecenia - start
(defun jk:DHR_CommWillStart (r c / cmd)
  (setq cmd 
    (cond
      ( (car c))
      ( (getvar "CMDNAMES"))
      (T nil)
    )
  )
  (if
    (wcmatch cmd "*DIM*")
    (jk:DHR_HatchHide)
  )
  (princ)
)
; --------------------------------------------------------------------- ;
; Reaktor polecenia - koniec
(defun jk:DHR_CommEnded (r c)(jk:DHR_HatchUnHide))
; --------------------------------------------------------------------- ;
; zerowanie reaktorow
(defun jk:DHR_CleanReact (r c)
  (mapcar 'vlr-remove-all
    '(:vlr-Command-Reactor :vlr-dwg-Reactor)
  )
  (jk:DHR_InitReactor nil)
)
; --------------------------------------------------------------------- ;
; ssget okna
(defun jk:DHR_ScreenSelect (/ p LM:viewportextents)
;; Viewport Extents  -  Lee Mac
;; Returns two WCS points describing the lower-left and
;; upper-right corners of the active viewport.
  (defun LM:viewportextents (/ c h v)
    (setq c (trans (getvar 'viewctr) 1 0)
          h (/ (getvar 'viewsize) 2.0)
          v (list (* h (apply '/ (getvar 'screensize))) h)
    )
    (list (mapcar '- c v) (mapcar '+ c v))
  )
;; --------
  (setq p (LM:viewportextents))
  (ssget "_C" (car p)(cadr p))
)
; --------------------------------------------------------------------- ;
; ukrywa HATCH 
(defun jk:DHR_HatchHide (/ h l)
  (if *jk-DH-Status*
    (if
      (jk:DHR_ScreenSelect)
      (if
        (setq h (ssget "_P" (list (cons 0 "HATCH"))))
        (progn
          (setq l (cd:SSX_Convert h 0)
          )
          (foreach % l (redraw % 2))
          (setq *jk-DH-HatchList* l)
        )
      )
    )
  )
)
; --------------------------------------------------------------------- ;
; odkrywa HATCH
(defun jk:DHR_HatchUnHide ()
  (if *jk-DH-HatchList*
    (progn
      (foreach % *jk-DH-HatchList* (redraw % 1))
      (setq *jk-DH-HatchList* nil)
    )
  )
)
; --------------------------------------------------------------------- ;
(princ)

Warto zwrócić uwagę, że reaktor nie modyfikuje rysunkowej bazy danych – jego działanie polegające na ukrywaniu kreskowania, wykorzystuje jedynie funkcję AutoLISP, o nazwie redraw.