AutoLISP

Obroty 3D / Rotate3D

Tworzenie modelu trójwymiarowego dowolnego obiektu staje się dziś już powszechną praktyką. Niemniej nadal duża część projektów wykonywana jest w tradycyjny sposób – jako obiekty dwuwymiarowe. W branży architektonicznej i budowlanej są to zwykle rzuty, przekroje i elewacje. Na ich podstawie można zbudować całkowicie trójwymiarowy model projektowanego obiektu. W praktyce wszystkie konieczne rzuty i widoki trzeba umieścić na płasczyźnie, a następnie ustawić je odpowiednio w przestrzeni. Pozwala to poprzez rzutowanie w dowolnym kierunku na kontrolę tworzonego modelu 3D. Zwykle najlepiej jest tworzyć bloki z poszczególnych widoków, aby potem w całości je obracać w przestrzeni. Widać to na animacji poniżej:

W AutoCAD do manipulacji obiekami w przestrzeni służą polecenia ROTATE3D i MIRROR3D. Jednak aby wyeliminować zbędne klikanie, polegajace na wskazywaniu punktów, wyborze obiektów itd. napisałem proste makro aby zautomatyzować ten proces. Ogólnym przyjętym założeniem jest obrót wskazango obiektu (tutaj zawsze wstawienia bloku) o 90 stopni w płaszczyżnie prostopadłej do płaszczyzny konstruktyjnej definicji bloku. Rzecz jasna płaszczyzna ta przechodzi przez punkt bazowy (wstawienia) bloku. Zdefiniowane polecenie BLR3 przy każdorazowym wskazaniu bloku obraca go o 90 stopni:

Kod makra jest krótki i stosunkowo prosty, a wygląda tak:


; ----------------------------------------------------------------------- ;
; by kojacek 2023/2024
(defun C:BLR3 (/ %e %o %i %s)
  (if
    (and
      (setq %e
        (car
          (entsel "\nSelect section to rotate:")
        )
      )
      (=
        (cdr (assoc 0 (entget %e)))
        "INSERT"
      )
    )
    (progn
      (setq %o (vlax-ename->vla-object %e)
            %i (vlax-get %o "InsertionPoint")
            %s (mapcar '+ %i (list 0 1 0))
      )
      (cd:SYS_UndoBegin)
      (vla-rotate3d %o
        (vlax-3d-point %i)
        (vlax-3d-point %s)
        (* 0.5 pi)
      )
      (cd:SYS_UndoEnd)
    )
    (princ "\nWrong selected - it is not a block.")
  )
  (princ)
)
; ------------------------------------------------------------------------ ;

Ponieważ obrót bloku wykonywany jest wokół osi, której pierwszy punkt jest punktem wstawienia, (a drugi przesuniety o jedną jednostkę w osi X), ważnym jest aby blok został wcześniej poprawnie zdefiniowany. Istotną rzeczą jest określenie punku bazowego (wstawienia). Punkt ten definiuje oś obrotu bloku. Po ustawieniu bloku w przestrzeni trójwymiarowej, od tego punktu zależą wartości poziomów:

Kilka słów o przestrzeniach 2D i 3D napisałem wcześniej już tu: Zrozumieć 3D : kod 210 – następny krok, oraz wcześniej TUTAJ. Także w wielu innych wpisach poruszyłem tematy dotyczace modelowania trójwymiarowego.

To jest #217 wpis na blogu

( . . . )

AutoLISP

(Nie)rysowanie

Krótki opis i prosty przykład programowego rysowania w AutoCAD-zie, bez tworzenia obiektów. Zatem jest to rodzaj pewnego rysowania, ale (po pierwsze) ulotnego, oraz (po drugie) nie w rysunku tylko na ekranie AutoCAD-a. Po trzecie (w końcu) rysowanie tylko wektorów – czyli jedynie odcinków określonych dwoma punktami.

Poniżej jako prosty przykład kod definicji polecenia TEMC , którego działanie widać na powyższej animacji. Do rysowania wektorów w aktualnej rzutni służą funkcje AutoLISP grdraw (wektor pomiędzy dwoma punktami), oraz grvecs (wiele wektorów). Tutaj została użyta tylko grdraw. Do odczytywania wartości z urządzenia wskazującego (tutaj współrzędnych podczas przeciągania kursora), służy funkcja grread. Ma ona także jeszcze inne zastosowanie – potrafi także odczytywać wartości klawiszy klawiatury. Odczytana współrzedna ekranu wykorzystywana jest do obliczenia w czasie rzeczywistym współrzędnych 48 punktów pomiędzy którymi rysowane są wektory, służące do aproksymacji okręgu (w rzeczywistości rysowany jest wielokąt foremny). Dla bardziej dokładnego odwzorowanie łuku czy okręgu trzeba użyc więcej punktów, co oczywiście spowalnia cały proces. Poniżej kod definicji polecenia TEMC:


; ----------------------------------------------------------------------- ;
(defun C:TEMC (/ e d p r c a l s)
  (if 
    (setq e (entsel "\nWybierz okrąg: ")) 
    (if 
      (= "CIRCLE" 
        (cdr (assoc 0 
          (setq d (entget (car e)))) 
        )
      ) 
      (progn 
        (setq p (cdr (assoc 10 d))
              r (cdr (assoc 40 d))
              c (cdr (assoc 62 d))
        )
        (while 
          (and 
            (setq s (grread t 13 0)) 
            (= (car s) 5) 
          ) 
          (redraw) 
          (setq a (cd:CAL_Sequence 0.0 48 (/ pi 24))
                l (mapcar
                     '(lambda (%)(polar (cadr s) % r))
                     a
                   )
                l (append (list (last l)) l) 
          )
          (while
            (/= (length l) 1)
            (grdraw
              (car l)
              (cadr l)
              (if (not c) 7 c)
              0
            )
            (setq l (cdr l)) 
          )
        )
      )
      (princ "\nTo nie jest okrąg.") 
    ) 
    (princ "\nNic nie wskazano. ") 
  ) 
  (princ) 
)
; ------------------------------------------------------------------------ ;

Ponieważ kursor jest dynamicznie przeciagany, wymagane jest aby przy każdorazowej zmianie współrzędnych (czyli narysowaniu okręgu) nastąpiło wymazanie poprzedniego.Dokonuje tego funkcja redraw. Mała zmiana w kodzie, polegająca na wyłączeniu tej funkcji, pokazuje w jaki sposób działa teraz polecenie TEMC.

Poniżej widać efekt – podczas przesuwania kursora na ekranie rysowane są okręgi, w każdym „przechwyconym” przez grread punkcie. Na koniec polecenie REGEN (albo REDRAW) pozwala je usunąć:

Wykorzystywanie grread oraz funkcji rysujących w aktywnej rzutni, nie jest pozbawione pewnych wad. Zasadniczą – brak jest trybów lokalizacji podczas przeciągania. Oznacza to że nie jest możliwe przecyzyjne określanie punktów w odniesieniu do rzeczywistych obiektów w rysunku. Oczywiście można stosować funkcję osnap, jednak w trakcie działania grread nie są one dynamicznie widoczne. Z tego powodu najlepiej jest stosować ten mechanizm raczej do wskazywania kierunku, niż określonego punktu. Istnieje w sieci kilka rozwiązań tego problemu, z programowo tworzonymi procedurami imitującymi standardowe tryby lokalizacji.

Poniżej przykłady zastosowania podobnego do omawianego tutaj sposobu wizualnego wyznaczania kierunku, oraz ilości obiektów w dwóch rodzajach szyku (prostokątnym i na zakładkę) w aplikacji służącej do projektowania terenów zieleni:

Do obliczania współrzednych wielokata odwzorujacego okrąg użyta jest tutaj funkcja o nazwie cd:CAL_Sequence która jest częścią biblioteki CADPL-Pack, a została już tutaj omówiona we wpisie Ciąg arytmetyczny. Oczywiście wymagane jest aby biblioteka była wcześniej wczytana.

To jest #216 wpis na blogu

( . . . )

AutoLISP

Warstwy – (masowa) zmiana cech

Przykładowy (więc) stosunkowo krótki program LISP do masowej (i wsadowej) zmiany cech warstw w rysunku. Operacje takie wykonywane w dużej ilości, potrzebne są w przypadkach dostosowania projektów do określonych standardów. Zwykle chodzi tu o podkłady architektoniczne, lub geodezyjne (tak jak na poniższym przykładzie). Potrzeba zautomatyzowania takich czynności została zasygnalizowana na jednym z cad-owych forum TUTAJ.

Działanie programu polega na odczycie danych z zewnętrznego pliku (wsadowego) z danymi potrzebnymi do ustalenia właściwości warstwy, przypisania tych właściwości do warstw, oraz zmiany cech obiektów rysunkowych. Domyślnie obiekty graficzne mają zmieniane cechy na BYLAYER (JAKWARSTWA), tak aby dziedziczyły właśności z warstwy na której się znajdują. Wszystkie dane zmian cech warstw znajdują się w pliku warstwy.dat, który musi znajdować się w bibliotecznej ścieżce poszukiwań AutoCAD-a. Plik ten można w dowolny sposób edytować aby dostosować program do własnych potrzeb. Przykładowy plik może wyglądać tak:

Struktura danych pliku warstwy.dat wygląda następująco:

Definicja zmian dla każdej warstwy zajmuje jedną linię i zaczyna się od znaku „*” (inne linie są ignorowane). Dane zajmują 5 pozycji i rozdzielone są znakami „;„. Pierwszą pozycją jes nazwa warstwy. Jeżeli w rysunku nie zostanie znaleziona warstwa o tej nazwie, program pomija w całości tę linię. Drugim elementem jest status warstwy. Dopuszczalnymi wartościami są 0 i 1. Program na ich posdtawie ustala widoczność warstwy. Trzecim elementem jest kolor warstwy, gdzie możliwe są dwa typy wartości – kolor ACI (AutoCAD Color Index od 1 do 255) albo truecolor postaci R,G,B. W tym przypadku separatorem jest przecinek – „,„. O kolorach ACI pisałem wcześniej TU. Kolejnym elementem jest nazwa rodzaju linii. Musi to być poprawna nazwa rodzaju linii istniejącego już w rysunku. Na końcu ustalana jest szerokość rodzaju linii, dopuszczalnymi wartościami są standardowe szerokości, podane w setnych milimetra (podobnie jak wartości zmiennych systemowych: CELWEIGHT lub LWDEFAULT). Szerokości linii opisywałem TUTAJ. Dla wszystkich danych (oprócz nazwy) można użyć znaku „%” i wtedy program nie dokonuje zmiany dla tego elementu.

Program został tak skonstruowany że dokonuje zmian tylko w przypadku poprawnie odczytanych danych, Stąd szczególnie ważnym jest aby uważnie i poprawnie tworzyć wpisy w pliku warstwy.dat. Rzecz jasna program można rozbudować aby dokonywał zmian cech w szerszym zakresie, czytał dane z innych (wskazanych) plików itp. Może być zatem podstawą do stworzenia większego narzędzia, do czego zachęcam. Kod programu wygląda tak:


; ------------------------------------------------------------------------------ ;
; MOD_WAR by kojacek 2022                                                        ;
; ------------------------------------------------------------------------------ ;
(defun C:MOD_WAR (/ d f)
  (if
    (setq f (findfile "warstwy.dat"))
    (if
      (setq d (cd:SYS_ReadFile nil f))
      (if
        (setq d
          (vl-remove-if
            '(lambda (%)(/= (substr % 1 1) "*")) d
          )
        )
        (progn
          (cd:SYS_UndoBegin)
          (foreach % d (LayChProp %))
          (cd:SYS_UndoEnd)
        )
        (princ "\nNiepoprawne dane dla warstw.")
      )
      (princ "\nNie można odczytać danych z pliku warstwy.dat")
    )
    (princ "\nNie znaleziono pliku warstwy.dat")
  )
  (princ)
)
; ----------------------------------------------------------------------------- ;
(defun LayChProp (Data / d s c v p g x y cl :color :tcolor :lweight)
  (defun :lweight (/ %1 %2 %3)
    (vl-remove-if 'minusp
      (if
        (setq %1
          (vl-sort
            (vl-remove-if-not
             '(lambda (%2)(wcmatch %2 "ACLNWT*"))
              (atoms-family 1)
            )'<
          )
        )
        (mapcar
          '(lambda (%3) (eval (read %3))) %1
        )
      )
    )
  )
  (defun :tcolor ()
    (vla-getinterfaceobject
      (vlax-get-acad-object)
      (strcat
        "AutoCAD.AcCmColor."
        (substr (getvar "ACADVER") 1 2)
      )
    )
  )
  (defun :color (i m / r)
    (if
      (<= (strlen i) 3)
      (progn
        (setq r (abs (atoi i)))
        (if m
          (if (and (>= r 0)(<= r 256)) r)
          (if (and (>= r 1)(<= r 255)) r)
        )
      )
      (progn
        (setq r (cd:STR_Parse i "," t))
        (if
          (and (listp r)(= 3 (length r)))
          (mapcar 'atoi r)
        )
      )
    )
  )
  (setq d (cd:STR_Parse Data ";" t)
        l (substr (car d) 2)
  )
  (if
    (= 5 (length d))
    (if
      (tblobjname "LAYER" l)
      (progn
        (setq s (cadr d)                                        ; lay-on-off
              v (vlax-ename->vla-object (tblobjname "LAYER" l)) ; lay-vlaxobj
              c (:color (caddr d) nil)                          ; lay-color
              p (cadddr d)                                      ; lay-ltype
              g (atoi (car (cddddr d)))                         ; lay-lwght
              x (ssget "_x"
                  (list
                    (cons 8 l)
                    (cons 410 (getvar "CTAB"))
                  )
                )
        )
        (if
          (member s '("0" "1"))
          (if (zerop (read s))
            (LayOnOff l)
          )
        )
        (if c
          (if
            (= (type c) 'INT)
            (vla-put-Color v c)
            (progn
              (setq cl (:tcolor))
              (vla-SetRGB cl (car c)(cadr c)(caddr c))
              (vla-put-TrueColor v cl)
            )
          )
        )
        (if
          (tblobjname "LTYPE" p)
          (vla-put-Linetype v p)
        )
        (if
          (and g (member g (:lweight)))
          (vla-put-LineWeight v g)
        )
        (if x
          (progn
            (setq x (cd:SSX_Convert x 1))
            (foreach % x
              (vla-put-Color % 256)
              (vla-put-LineType % "ByLayer") 
              (vla-put-LineWeight % -1)
            )       
          )
        )
      )
    )
  )
)
; ----------------------------------------------------------------------------- ;
(defun LayOnOff (Lay / e d)
  (if
    (setq e (tblobjname "LAYER" Lay))
    (progn
      (setq d (entget e))
      (setq d
        (subst
          (cons 62
            (* -1 (cdr (assoc 62 d)))
          )
          (assoc 62 d)
        d)
      )
      (entmod d)
    )
  )
)
; ----------------------------------------------------------------------------- ;
(princ)

Program wymaga użycia biblioteki CAD-PL Pack. O warstwach w różnych aspektach wspominałem w tych wpisach.

To jest #215 wpis na blogu

( . . . )

AutoCAD

AutoCAD (+)

Wśród wszystkich poleceń AutoCAD-a, jest pewna ich grupa, mniej (lub całkowicie) nieznana większości użytkowników. Grupa tych poleceń wyróżniająca się przedrostkiem + (plus), dodanym do kilku standardowych poleceń. Większość tych poleceń ma znaczenie dla programujących, niemniej (wydaje się) że każdy użytkownik powinien przynajmniej znać takie możliwości. Poniżej ich krótki (alfabetyczny) opis.

+CONSTRAINTSETTINGS – po wywołaniu (jak większość tu omówionych) polecenie wyświetla zgłoszenie Tab Index <0> (Indeks tabeli <0>), określające która zakładka okna dialogowego będzie aktywna po wyświetleniu okna (tutaj) dla polecenia CONSTRAINTSETTINGS (+USTAWWIĄZGEOM). Standardowo samo polecenie (bez plusa) wyświetla okna z aktywną pierwszą zakładką. Użycie polecenia z plusem i określenie, (poprzez liczbę) numeru zakładki wyświetla ją jako aktywną. Pamiętać trzeba że pierwsza zakładka ma zawsze numer 0.

_+CONSTRAINTSETTINGS (+USTAWWIĄZGEOM)

+CUSTOMIZE – Uruchamia okno dialogowe aby umożliwić dostosowanie palet narzędzi i grup palet narzędzi. Pomimo tego że okno ma tylko jedną zakładkę wyświetlane jest zgłoszenie Tab Index. Oczywiście zawartość standardowych palet i grup palet wygląda inaczej w AutoCAD LT, AutoCAD, czy specjalizowanych AutoCAD Architecture, AutoCAD Mechanical, AutoCAD Map czy innych.

_+CUSTOMIZE (+ADAPTACJA)

+DSETTINGS – Okno (chyba wszystkich) ustawień rysunkowych AutoCAD-a. Tutaj dużo większy wybór pozycji zakładek:

_+DSETTINGS (+USTAWIENIARYS)

+FINDUI – W AutoCAD-zie, szeroko rozbudowania pomoc posiada kilka sprytnych udogodnień. Jednym z nich jest wyszukiwanie przycisku polecenia na wstążce. Wymagane jest podanie jego identyfikatora w pliku dostosowania.

_+FINDUI (+FINDUI)

Po znalezieniu prawidłowego odniesienia wyświetlana jest ruchoma strzałka. Mechanizm ten można prześledzić wywołując pomoc AutoCAD-a, która wszędzie jest tak skonstruowana, że prawidłowo działa w każdej dostępnej wersji językowej.

+LAYER – Jako słowa kluczowe wyświetla zdefiniowane filtry warstw i z nich pozwala ustalić filtr aktualny (zastosowany w oknie (lub) palecie) Menedżera właściwości warstw. Ze wzgledu na to że słowa kluczowe tworzone są „w locie” na podstawie istniejących filtrów, ich skróty, nie zawsze mogą działajać zgodnie z oczekiwaniami.

_+LAYER (+WARSTWA)

Alternatywnie można użyć nieudokumentowanej opcji _Filter oraz _Set polecenia _-LAYER (-WARSTWA). Ustala ona określony filtr jako aktualny.

+OPTIONS – Działanie analogiczne do wcześniej opisanych poleceń z zakładkami w oknach dialogowych.

_+OPTIONS (+OPCJE)

+PUBLISH – Publikuje rysunki w postaci plików DWF, DWFx i PDF lub na drukarkę / ploter, tworząc ich kolekcję (zestaw DSD). Polecenie z plusem pozwala na zmianę ustawień w oknie dialogowym przez użytkownika. Jest zatem alternatywą wywołania polecenia _-PUBLISH, (z minusem), gdzie ustawienia muszą być ustalone wcześniej, a są tylko przekazywane jako opcje w linii poleceń.

+PUBLISH (+OPUBLIKUJ)

+PURGE – podobnie jak poprzednio. Można wyświetlić aktywną jedną z dwóch zakładek, oraz pozostawić ustawienia użytkownikowi, w przeciwieństwie do _-PURGE (z minusem).

_+PURGE (+USUŃ)

+RIBBON – Przełącznik aktywnych paneli wstążki. Podobnie jak w +FINDUI wymagane jest podanie prawidłowego identyfikatora w pliku dostosowania. Konieczny jest zatem pewien zasób wiedzy na temat struktury plików cuix. Dodatkowo poprzez odczyt zmiennych systemowych i innych ustawień, powinno się sprawdzić status wstążki (nazwa / aktywna / ukryta / widoczna / dostępne panele itp.)

_+RIBBON (+WSTĄŻKA)

+SAVEAS – okno dialogowe zapisu pliku. Polecenie pozwala ustalić szereg proponowanych ustawień domyślnych dla wyświetlanego okna, pozostawiając także możliwość ich edycji przez użytkownika. Proponowane formaty (wsteczne) do zapisu, oraz ich kolejne opcje przedstawia obraz poniżej:

_+SAVEAS (+NZAPISZ)

+VPORTS – ostatnie „polecenie z plusem” – okno ustawień rzutni:

_+VPORTS (+RZUTNIE)

Tym kończę krótki spis (i opis) autocadowych „poleceń z plusem” (stan na 2022). Prawdopodobnie większość użytkowników nigdy ich nie będzie używać, niemniej programującym AutoCAD-a, w określonych sytuacjach mogą okazać się przydatne.

To jest #214 wpis na blogu

( . . . )

AutoLISP

TEXT 2 3DSOLID

Dzisiaj krótko o szybkim modelowaniu prostych prymitywów brył w AutoCAD-zie… polegającym na konwersji wybranych tekstów na… trójwymiarowe bryły właśnie. Oczywiście teksty muszą spełniać pewne warunki, (ogólnie) opisujace geometrię brył (położenie i wielkość). Jest to proces odwrotny niż zwykle spotykany, czyli opiywanie właściwości obiektów.

Tekstowy „opis” bryły 3D w tym przypadku, pobierany jest z wybranego obiektu typu TEXT. Warstwa, położenie oraz obrót, są takie same jak dla tekstu, wartości geometrii (szerokość, wysokość i długość) określone są w wartości (treści) tekstu. Warunkiem koniecznym jest aby fragment tekstu zawierał (w jednym ciągu) trzy liczby rozdzielone dwoma znakami „x”. Teksty które nie spełniają tego warunku są pomijane.

Zdefiniowane jest polecenie TXT2SOL. Po wywołaniu należy wskazać teksty. Program sprawdza czy tekst spełnia warunki konieczne do utworzenia BOX-a, i (po ich spełnieniu) je tworzy. (Krótki) kod makra (w całości) wygląda tak:


; -------------------------------------------------------------------------------------- ;
; Convert TEXT to 3DSOLID by kojacek 2022                                                ;
; -------------------------------------------------------------------------------------- ;
(defun C:TXT2SOL ()(jk:SOL_Text->Box)(princ))
; -------------------------------------------------------------------------------------- ;
(defun jk:SOL_Text->Box (/ s CMD)
  (princ "\nConvert text to 3D Solid Box...")
  (if
    (setq s (jk:SOL_SGet-Text))
    (progn
      (setq CMD (getvar "CMDECHO"))
      (cd:SYS_UndoBegin)
      (setvar "CMDECHO" 0)
      (foreach % s (jk:SOL_3DBox-Data %))
      (setvar "CMDECHO" CMD)
      (cd:SYS_UndoEnd)
    )
    (princ "\nSelected objects are not correct.")
  )
)
; -------------------------------------------------------------------------------------- ;
(defun jk:SOL_3D-Text-p (Str / l s :a)
  (defun :a (i)
    (cd:STR_Parse
      (vl-list->string
        (mapcar
         '(lambda (%)
            (if
              (or
                (>= % 58)
                (<= % 47)
              ) 37 %
            )
          )
          (vl-string->list i)
        )
      ) "%" nil
    )
  )
  (if
    (wcmatch
      (setq s (strcase Str))
      "*#X#X#*,*#X##X#*,*#X###X#*,*#X####X#*"
    )
    (progn
      (setq l (cd:STR_Parse s "X" nil))
      (mapcar
        'read
        (list
          (last (:a (car l)))
          (cadr l)
          (car (:a (last l)))
        )
      )
    )
  )
)
; -------------------------------------------------------------------------------------- ;
(defun jk:SOL_SGet-Text (/ s l)
  (if
    (setq s
      (ssget
        (list
          (cons 0 "TEXT")
          (cons 410 (getvar "CTAB"))
        )
      )
    )
    (setq l (cd:SSX_Convert s 0)
          l (vl-remove-if
              'numberp
              (mapcar
               '(lambda (%)
                 (if
                   (jk:SOL_3D-Text-p
                     (cdr (assoc 1 (entget %)))
                   ) % 0
                  )
                ) l
              )
            )
    )
  )
)
; -------------------------------------------------------------------------------------- ;
(defun jk:SOL_3DBox-Data (Ent / d b x c o e)
  (setq d (entget Ent)
        x (jk:SOL_3D-Text-p (cdr (assoc 1 d)))
        b (acet-geom-textbox d 0)
        c (acet-geom-midpoint (car b)(caddr b))
        e (entlast)
  )
  (command "_BOX" "_c" c "_l" (caddr x)(cadr x)(car x))
  (setq o (entlast))
  (if
    (/= o e)
    (progn
      (setq v (vlax-ename->vla-object o))
      (vla-rotate v
        (vlax-3d-point c)(cdr (assoc 50 d))
      )
      (vla-put-Layer v (cdr (assoc 8 d)))
    )
  )
)
; -------------------------------------------------------------------------------------- ;
(princ)

Rzecz jasna konieczne jest załadowanie CADPL-Pack-a. Potrzebne jest również wcześniejsze wczytanie pakietu ExpressTools AutoCAD-a (wykorzystywane są dwie funkcje acet-). „Sprytne” umieszczenie tekstów, pozwala na szybkie tworzenie brył, z jednoczesnym precyzyjnym położeniem elementów. Jakiś szybki glulam portal frame? Voila:

Zautomatyzowany sposób opisywania brył 3D (typu box) omawiałem we wpisie Zliczanie 3DSOLID. Natomiast ogólnie o prymitywach brył w AutoCAD-zie (typu box), słów kilka można znaleźć tutaj: AutoCAD 3D.

To jest #213 wpis na blogu

( . . . )

AutoLISP

Smart ERaser

Użytkownicy AutoCAD-a (choć nie tylko), są przekonani że jest to doskonałe narzędzie do tworzenia (rysowania i modelowania), a możliwości oprogramowania tych procesów, to już w ogóle bajka… Zgadzając się z tym w ogólności, poświęcę jednak dzisiejszy wpis… wymazywaniu obiektów, ze wskazaniem, że automatyzacja tegoż może być też niezłą zabawą…

Tak jak widać na (ruchomym) obrazie powyżej, chodzi o bardziej kontrolowany proces usuwania obiektów, niż jest to możliwe standardowo. Jest to wybór (wszystkich) obiektów ze wskazanej warstwy (przez wybranie jednego obiektu), z filrowaniem rodzaju obiektu (z możliwością zawężenia wyboru w oknie dialogowym). Normalnie, polecenie AutoCAD-a WYMAŻ (_ERASE), pozwala usunąć tylko wskazane obiekty. Rzecz jasna, utworzenie potrzebnego rozszerzonego zbioru wskazań (dla ERASE) może się odbywać na wiele sposobów (SELECT / QSELECT / SSGET), jednak zawsze wymaga to więcej pracy, niż skorzystanie z przedstawionego tu programu.

Jego działanie ogranicza się tylko do wskazania dowolnego obiektu w rysunku (z niezamkniętej warstwy) przeznaczonego do usunięcia. Program automatycznie tworzy zbiór wskazań wszystkich obiektów (z tej warstwy), dzieląc go na grupy znalezionych obiektów. Następnie (dynamicznie) tworzone jest okno dialogowe, w którym wyświetlana jest warstwa, oraz typy obiektów (z ilością występowania) w wycinkach typu toggle. Domyślnie wszystkie obiekty są wybrane (zaznaczone do usunięcia), wybór można jednak swobodnie ograniczyć.

Dla programujących w LISP-ie, ciekawostką będzie fakt wykorzystania jednej i tej samej funkcji obsługi wycinków toggle, dla nich wszystkich niezależnie od ich ilości (jest różna w zależności od rodzajów wybranych obiektów). Ta sama funkcja wykorzystana jest także do obsługi klawisza akceptującego [OK]. Mechanizm ten pozwala na kontrolowanie jego zachowania (aktywny / nieaktywny) zależnie od stanu wyboru wycinków wybierających. Jest to zilustrowane na grafice powyżej.

Wywołanie programu poleceniem: SER. Kod programu w całości przedstawiam poniżej:


; ---------------------------------------------------------------------------------- ;
; SmartERaser.lsp by kojacek 2022                                                    ;
; ---------------------------------------------------------------------------------- ;
(defun C:SER ()(jk:SER_Main)(princ))
; ---------------------------------------------------------------------------------- ;
(defun jk:SER_GetSelectLay (/ s)
  (if
    (setq s
      (car (entsel "\nSelect object:"))
    )
    (cdr (assoc 8 (entget s)))
  )
)
; ---------------------------------------------------------------------------------- ;
(defun jk:SER_isLock (Lay)
  (= :vlax-true
    (vla-get-Lock
      (vla-item
        (cd:ACX_Layers) Lay
      )
    )
  )
)
; ---------------------------------------------------------------------------------- ;
(defun jk:SER_ssGet (Lay LObj / ss l)
  (setq l
    (list
      (cons 8 Lay)
      (cons 410 (getvar "CTAB"))
    )
  )
  (if
    (setq ss
      (ssget "_x"
        (if
          (not LObj)
          l
          (append
            (list
              (cons 0 (cd:STR_ReParse LObj ","))
            )   l
          )
        )
      )
    )
    (cd:SSX_Convert ss 0)
  )
)
; ---------------------------------------------------------------------------------- ;
(defun jk:SER_ObjLays (LObj)
  (mapcar
    '(lambda (%)
       (cdr
         (assoc 0 (entget %))
       )
    ) LObj
  )
)
; ---------------------------------------------------------------------------------- ;
(defun jk:SER_ObjLaysItem (LObj / l :i)
  (defun :i (o l / %1)
    (setq %1 (vl-remove o l))
    (strcat
      o " ("
      (itoa (- (length l)(length %1)))
      ")"
    )
  )
  (setq l (LM:Unique LObj))
  (acad_strlsort
    (mapcar
      '(lambda (%)
         (:i % LObj)
      ) l
    )
  )
)
; ---------------------------------------------------------------------------------- ;
(defun jk:SER_ObjDlgData (Lst / s k e l)
  (setq s ":toggle{label=\""
        k "\";key=\""
        e "\";}"
  )
  (mapcar
   '(lambda (%)
      (strcat
        s
        %
        k
        (car (cd:STR_Parse % " " t))
        e
      )
    ) Lst
  )
)
; ---------------------------------------------------------------------------------- ;
(defun jk:SER_Main (/ o l s i a r)
  (if
    (setq o (jk:SER_GetSelectLay))
    (if
      (jk:SER_isLock o)
      (princ
        (strcat "\nLayer \"" (strcase o) "\" is locked.")
      )
      (progn
        (setq s (jk:SER_ssGet o nil)
              l (jk:SER_ObjLays s)
              i (length l)
        )
        (if
          (= 1 i)
          (jk:SER_Erase (list s) o)
          (if
            (setq r (jk:SER_Dialog l o))
            (progn
              (setq s (jk:SER_ssGet o r))
              (jk:SER_Erase (list s) o)
            )
            (princ "\nCancelled.")
          )
        )
      )
    )
    (princ "\nNothing selected.")
  )
)
; ----------------------------------------------------------------------------------- ;
(defun jk:SER_Erase (Lst Lay / i)
  (setq i (itoa (apply '+ (mapcar 'length Lst))))
  (cd:SYS_UndoBegin)
  (foreach % Lst
    (foreach %1 % (entdel %1))
  )
  (cd:SYS_UndoEnd)
  (princ
    (strcat
      "\n" i
      " objects erased from \""
      (strcase Lay)
      "\" layer."
    )
  )
)
; ----------------------------------------------------------------------------------- ;
(defun jk:SER_Dialog (Lst Lay / a b d f i m r w x :c :o :r)
  (defun :c ()
    (mapcar
      '(lambda (%)
         (cons % (read (get_tile %)))
      ) x
    )
  )
  (defun :o (/ j)
    (setq j (apply '+ (mapcar 'cdr (:c))))
    (mode_tile "accept"
      (cond
        ( (zerop j) 1)
        (t 0)
      )
    )
  )
  (defun :r (n)
    (mapcar 'car
      (vl-remove-if '(lambda (%)(zerop (cdr %))) n)
    )
  )
  (setq l (jk:SER_ObjLaysItem Lst)
        x (LM:Unique Lst)
        a (jk:SER_ObjDlgData l)
        i (itoa (1- (length a)))
        b "Smart ERaser 1.00"
        w "width=12;"
  )
  (cond
    ((not
       (and
         (setq f
           (open
             (setq m (vl-FileName-MkTemp nil nil ".dcl")) "w"
           )
         )    
         (foreach % 
           (list
             (strcat
              "ser:dialog{label=\"" b
              "\";:column{:boxed_row{label=\"Layer:\";:text{label=\""
              (strcase Lay)
              "\";}}:row{:boxed_column{label=\"Objects to erase:\";"
              (apply 'strcat a)              
              "}:column{:spacer{height=" i
              ";}:column{height=2;fixed_height=true;:ok_button{" w
              "}:cancel_button{" w
              "label=\"Cancel\";}}}}}}"
              )
            )
            (write-line % f)
          )
          (not (close f))
          (< 0 (setq d (load_dialog m)))
          (new_dialog "ser" d ""
            (cond
              ( %p )
              ( (quote (-1 -1)) )
            )
          )
        )
      )
    )
    ( T
      (foreach % x
        (set_tile % "1")
        (action_tile % "(:o)")
      )
      (action_tile "accept"
        "(setq r (:c) %p (done_dialog 1))"
      )
      (action_tile "cancel"
        "(setq %p (done_dialog 0))"
      )
      (start_dialog)
    )
  )
  (if r (:r r))
)
; ---------------------------------------------------------------------------------- ;
; http://www.lee-mac.com/uniqueduplicate.html                                        ;
; Unique  -  Lee Mac                                                                 ;
; Returns a list with duplicate elements removed.                                    ;
(defun LM:Unique ( l / x r )
  (while l
    (setq 
      x (car l)
      l (vl-remove x (cdr l))
      r (cons x r)
    )
  )
  (reverse r)
)
; ---------------------------------------------------------------------------------- ;
(princ)

Oczywiście do poprawnego działania, konieczne jest wcześniejsze załadowanie CADPL-Pack’a.

To jest #212 wpis na blogu

( . . . )

AutoLISP

TEXT to ATTRIB

Dzisiejszy wpis traktuje o zmianie definicji bloku polegającej na zastąpieniu jego obiektów tekstowych na atrybuty. Rzecz jasna chodzi tu o proces wykonywany programowo, automatycznie po wskazaniu wstawienia bloku. Opisany tu przykład to zestaw bloków 2D generowanych przez aplikację AutoCAD-a, z trójwymiarowego modelu. Bloki służą do tworzenia dokumentacji w obszarze papieru. Wszystkie obiekty 3D składajace się na rzeczywisty model, mają swoje identyfikatory, które w bloku 2D są reprezentowane przez statyczny tekst. Użycie w ich miejsce atrybutów przynosi szereg korzyści – takie opisy można przesuwać, zmieniać ich treść oraz inne cechy (styl tekstu, kolor, wielkość itp.), niezależnie dla każdego wstawienia bloku. Do takiej szybkiej konwersji napisałem prosty program LISP-owy.

Na dwóch prezentowanych tu animacjach widać różnice dla bloków przed konwersją tekstów (powyżej), oraz (poniżej) po omawianej zmianie. Przedstawiony przykład jest prosty, zmianie podlegają dwa z trzech obiektów tekstowych w bloku. Jak widać na animacji poniżej po wskazaniu bloku, w sposób praktycznie niezauważalny, w wybranym bloku pojawiła się możliwość wyboru uchwytami i przesuwania etykiet tekstowych:
Ponieważ program napisałem na szczególne potrzeby, ma on swoje z góry założone ograniczenia. Są to między innymi:
  • wybrany blok musi znajdować się na warstwie zawierającej w nazwie ciąg tekstowy „User”.
  • blok nie może wcześniej posiadać zdefiniowanych atrybutów.
  • w bloku musi znajdować się co najmniej jeden obiekt tekstowy.
  • zmianie na atrybut podlegają teksty o dowolnej wysokości (tylko jeżeli jest jeden obiekt tekstowy w bloku), lub tylko te o wysokości 60.0 jednostek (gdy w bloku jest ich wiecej).
  • atrybuty tworzone są w sposób automatyczny, i są kolejno numerowane (tagi i zgłoszenia). Wartością przypisaną każdemu atrybutowi jest wartość tekstu źródłowego.
  • Wszystkie cechy atrybutu (punkt wstawienia, dopasowanie, styl tekstu, wysokość), są dziedziczone z obiektu tekstu źródłowego.

Poniżej kod programu – definicja polecenia BL-TTATT:


; ---------------------------------------------------------------------------- ;
; Converts block's TEXT objects to ATTRIB. by kojacek 2022                     ;
; ---------------------------------------------------------------------------- ;
(defun c:BL-TTATT (/ e c d)
  (setq c (getvar "CMDECHO"))
  (if
    (and
      (setq e (car (entsel "\nSpecify a block:")))
      (= "INSERT" (cdr (assoc 0 (setq d (entget e)))))
    )
    (if
      (null (cd:BLK_GetAttEntity e))
      (if
        (wcmatch (cdr (assoc 8 d)) "*User*")
        (if
          (cd:BLK_GetEntity
            (cdr (assoc 2 d))
            "TEXT"
          )
          (progn
            (cd:SYS_UndoBegin)
            (if
              (jk:BLK_Txt2Att e 60.0)
              (progn
                (setvar "CMDECHO" 0)
                (command "_ATTSYNC" "_S" e "")
                (setvar "CMDECHO" c)
                (princ ".. Block updated. ")
              )
            )
            (cd:SYS_UndoEnd)
          )
          (princ "\nThere are no texts in the block.")
        )
        (princ "\nObject on unsupported layer.")
      )
      (princ "\nSpecify a block with no attributes.")
    )
    (princ "\nThis is not a block.")
  )
  (princ)
)
; ---------------------------------------------------------------------------- ;
(defun jk:BLK_Txt2Att (En Size / d i j a n b %1 %2 %3)
  (setq d
    (cd:BLK_GetEntity
      (setq n (cdr (assoc 2 (entget En))))
      "TEXT"
    )
        i (length d)
        j (mapcar 'itoa (cd:CAL_Sequence 1 i 1))
        b (vla-item (cd:ACX_Blocks) n)
  )
  (if
    (> i 1)
    (setq d 
      (vl-remove-if
        '(lambda (%)
           (/=
             (vla-get-Height
               (vlax-ename->vla-object %))
             Size
           )
        ) d
      )
    )
  )
  (if d
    (progn
      (foreach % d
        (setq %2 (vlax-ename->vla-object %)
              %3 (nth (vl-position % d) j)
        )
        (setq %1
          (vla-addattribute b
            (vla-get-Height %2)
            16
            (strcat "Value_" %3)
            (vla-get-InsertionPoint %2)
            (strcat "Tag_" %3)
            (vla-get-TextString %2)
          )
        )
        (foreach %%
          '("Layer" "Color" "StyleName"
            "Alignment" "TextAlignmentPoint"
          )
          (vlax-put-property %1 %%
            (vlax-get-property %2 %%)
          )
        )
        (vla-put-Constant %1 :vlax-false)
        (vla-put-LockPosition %1 :vlax-false)
        (vla-delete %2)
      )
      (= 2
        (logand 2
          (cdr
            (assoc 70
              (entget
                (tblobjname "BLOCK" n)
              )
            )
          )
        )
      )
    )
  )
)
; ---------------------------------------------------------------------------- ;

Rzecz jasna po zmianie definicji bloku, wstawienia muszą zostać zaktualizowane poleceniem _ATTSYNC (ATRSYNC), co jest też uwzględnione w kodzie. Program korzysta z funkcji bibliotecznych zdefiniowanych w CADPL-Pack. Prezentowany program można dostosować do własnych potrzeb (na przykład aby zamienić wszystkie teksty w bloku, wystarczy w wywołaniu funkcji jk:BLK_Txt2Att jako argument Size użyć wartości ujemnej lub zerowej).

O blokach z atrybutami, o samych atrybutach pisałem TUTAJ już wielokrotnie.

To jest #211 wpis na blogu

( . . . )

AutoLISP

FIELD-y i zbrojenia…

FIELD-y czyli pola danych już niejednokrotnie tutaj gościły. Dzisiaj przedstawię przykład inteligentnego (na swój sposób) mechanizmu wzajemnego powiązania trzech prostych obiektów graficznych będących szkicem prętów zbrojeniowych.

Trzy elementy czyli: (1) dwuwymiarowa polilinia będąca uproszczoną grafiką pręta zbrojeniowego, (2) wymiar określający kierunek, położenie i rozsztaw prętów, oraz (3) etykieta opisowa (MTEXT), która spina wszystkie podstawowe informacje. Które to – dzięki zastosowaniu FIELD-ów, mogą być dynamicznie aktualizowane, co tworzy nową „żywą” jakość prostej (w końcu) grafiki.

Powyższa grafika ilustruje jak zmiany pewnych parametrów, wpływają na inne. Zmiana rozstawu prętów (przy tej samej długości rozłożenia) zmienia ilość potrzebnych prętów. Podobnie (ale w drugą stronę) – zmiana długości rozłożenia (ale przy tym samym rozstawie), także wpływa na ilość prętów. Oczywiście podstawowe parametry, jak srednica, czy długość pręta, są także dynamicznie odzwierciedlane przez pola danych.

Podsumowując korzyści płynące ze stosowania takich rozwiązań, zwracam uwagę na:

  • uniknięcie powstawania błędów charakterystycznych dla opisów statycznych – najważniejsze parametry szkicu obiektów, odzwierciedlają ich rzeczywiste cechy.
  • automatyzaję obliczania ilości prętów – ten proces dokonuje się właściwie sam.
  • wizualizację zmian – użytkownik dokonuje edycji obiektów modelu szkicu (pręt / wymiar), a opis jest dynamiczną ilustracją ich stanu.
  • dokładność i precyzja wykonywanych modeli.
  • szybkość tworzenia asocjacji obiektów.

O ile ostatni z punktów jest prawdziwy dla FIELD-ów w ogólności, to dopiero oprogramowanie tego procesu, daje dopiero prawdziwy obraz uzyskanej efektywności. Pokazany tutaj przykład to program LISP-owy który napisałem w celu zautomatyzowania mechanizmu tworzenia opisów prętów zbrojeniowych. Główne cechy to minimalizacja „klikologii” charakterystycznej (niestety) dla tworzenia pól danych w standardowy sposób. Tutaj – wystarczy wskazać dwa obiekty (pręt i wymiar), a opis jest tworzony automatycznie z domyślnymi wartościami, które to (równie szybko) można zmienić w (minimalistycznym) dynamicznie tworzonym oknie dialogowym. Co ważne – zmiany są widoczne podczas aktywności okna, co ułatwia ich śledzenie. Rzecz jasna (czego nie widać na animacjach) całość jest szerzej oprogramowana, jeżeli chodzi o wybory obiektów (ich rodzaje / warstwy itp.), listy prętów, kontrolę numeracji prętów i wartości pól edycyjnych.

Jak wcześniej wspomniałem o FIELD-ach pisałem wcześniej: TUTAJ – o opisywaniu danych polilinii. TUTAJ – o tworzeniu pochylenia dynamicznego. Zaś TU – o stosowaniu pól danych w blokach. Jest też bardzo stary (ale wiem że lubiany) LISP do obliczeń powierzchni. Zaś w TYM wpisie, opisywałem usuwanie FIELD-ów.

To jest #210 wpis na blogu

( . . . )

AutoLISP

BAL (Block-Attrib-Layout)

Czy możliwa jest jednoczesna edycja wielu bloków z atrybutami w różnych układach (layoutach)… (przy okazji) zmieniając im nazwy? Takie rzeczy to tylko… LISP-em… Rzeczywiście można oprogramować takie działanie – poniżej przykład:

Typowa sytuacja – w dokumencie znajduje się klika Layout-ów, będących arkuszami z tabliczkami rysunkowymi. Tabliczki są blokami z atrybutami, których część wartości jest ustalana w momencie powstawania dokumentu w projekcie. Druga część informacji wypełniana jest automatycznie podczas publikacji (druku) dokumentu. Trzecia – praktycznie wypełniana ręcznie, przez użytkownika każdorazowo podczas tworzenia kolejnych układów (layout-ów).

W pokazanym tutaj przykładzie, jest to nazwa rysunku oraz jego numer. Działanie programu polega na wybraniu wszystkich bloków tabliczek rysunkowych, we wszystkich layoutach, pobranie wartości atrybutów (tutaj tylko tych dwóch pól), i wykonaniu zestawienia w oknie dialogowym. W oknie tym można dokonać zmiany wartości atrybutów, dla każdego bloku w layoucie, a także zmienić jego nazwę. Proces zmian dokonywany jest dynamicznie podczas aktywności okna dialogowego (uważny czytelnik zauważy zmianę w nazwie aktualnej zakładki (SP3GF)…

Narzędzie to, pozwala szybko porównać zawartość kilku bloków z różnych układów, i w razie konieczności umożliwia dokonywanie zmian. Wydaje się że takie całościowe spojrzenie, jest o wiele wygodniejsze niż żmudne przeglądanie każdego bloku w każym layoucie… Program wykorzystuje (rzecz jasna) CADPL-Pack’a, oczywiście przy użyciu tej samej ilości nawiasów zamykających co otwierających… 😉

To jest wpis #209

( . . . )

AutoCAD, AutoLISP

Zestawienie stali zbrojeniowej

Tworzenie dokumentacji projektowej zbrojenia w konstrukcjach budowlanych, to proces stosunkowo złożony i skomplikowany. Duża ilość różnego rodzaju obiektów (wielkość, geometria, położenie w przestrzeni, ilość) w modelu (2 lub 3D), wymagają szczególnej precyzji na każdym etapie projektowania. Wszelkie działania automatyzujące pracę projektanta, zwiększają jakość dokumentacji, wychwytują i usuwają błędy, oraz zwalniają go od powtarzalnych i żmudnych działań.

Do takich działań można zaliczyć niewątpliwie tworzenie róznego rodzaju zestawień, tutaj zestawienia stali zbrojeniowej. Na animacji powyżej widać działanie okna dialogowego programu LISP-owego (a jakże!) do tworzenia zestawień stali zbrojeniowej. Jest to póki co, swego rodzaju prototyp, który mam nadzieję będzie w miarę potrzeb rozwijany.

Z programowego punktu widzenia, dużym wyzwaniem jest tutaj, manipulacja na danych pozyskiwanych z opisów rysunku AutoCAD-a. Dane (w formie przekształcanych list) są odpowiednio przetwarzane (przeszukiwanie, porównywanie ze wzorcem, zastępowane, sortowanie, odwracanie, przeliczanie itp.), a w końcu po obróbce wyświetlane w dynamicznie tworzonym oknie dialogowym. Tutaj (trzeba przyznać) LISP wydaje się – bardzo dobrze się sprawdził. To kolejny przykład że można (i trzeba) wykorzystywać wszelkie możliwości automatyzacji.

Myślę że w przyszłości będę miał możliwość szerzej opisać działanie tego programu.

To jest wpis numer: 208

( . . . )

AutoLISP

Pomiar długości krzywej – praktycznie

Praktyczny przykład zastosowania LISP-a do automatyzowania czynności. Tutaj pomiar długości krzywej reprezentującej dowolne medium, pomiedzy wskazanymi punktami określajacymi granice działek na mapie geodezyjnej. Zdefiniowane polecenie wymaga wybrania mierzonej linii (tutaj polilinii), oraz kolejno wskazywania punktów (podobnie jak w poleceniu tworzącym wymiary szeregowe). Pomiędzy wskazanymi punktami wstawiany jest tekst z wartością będącą zmierzoną długością. Działanie ilustruje poniższa animacja:

Krótki kod programu wymaga wcześniejszego załadowania CADPL-Pack’a, a całość wygląda tak:


; ------------------------------------------------------------------------- ;
; by kojacek - 2021                                                         ;
; ------------------------------------------------------------------------- ;
(defun C:DLK (/ :addanno :measure :getdist :getpoints :selcurve)
  (defun :addanno (Pts Val Lay Col / h a b x d i)
    (setq h (getvar "TEXTSIZE")
          a (car Pts)
          b (cadr Pts)
          x (angle a b)
          d (* 0.5 (distance a b))
          i (polar a x d)
    )
    (cd:ENT_MakeText
      (getvar "CTAB") Val i h 0.0
    )
  )
  (defun :measure (/ l p d u)
    (if
      (setq l
        (:selcurve
          "\nWybierz krzywą:"
          (list "LINE" "LWPOLYLINE" "SPLINE" "ARC")
        )
      )
      (progn
        (cd:SYS_UndoBegin)
        (redraw l 3)
        (while
          (setq p (:getpoints (cadr p)))
          (progn
            (setq d
              (cd:CON_Real2Str (:getdist p l) 2 2)
                  u (cdr (assoc 8 (entget l)))
            )
            (:addanno p d u 2)
          )
        )
        (redraw l 4)
        (cd:SYS_UndoEnd)
      )
      (princ "\nNie wskazano poprawnej krzywej.")
    )
  )
  (defun :getdist (Pts Ename / a b r)
    (setq a (osnap (car Pts) "_nea")
          b (osnap (cadr Pts) "_nea")
    )
    (setq r
      (abs
        (-
          (vlax-curve-getDistAtPoint Ename a)
          (vlax-curve-getDistAtPoint Ename b)
        )
      )
    )
  )     
  (defun :getpoints (In / s e)
    (if
      (if In
        (setq s In)
        (setq s (getpoint "\nWskaż pierwszy punkt pomiaru:"))
      )
      (if
        (setq e (getpoint s "\nNastępny punkt:"))
        (if
          (not
            (equal s e 0.01)
          )
          (list s e)
        )
      )
    )
  )
  (defun :selcurve (Msg Lst / e)
    (if
      (and
        (setq e (car (entsel Msg)))
        (member
          (cdr (assoc 0 (entget e)))
          Lst
        )
      ) e
    )
  )
  (:measure)
  (princ)
)
; ------------------------------------------------------------------------- ;

O pomiarze długości krzywej pisałem już jakiś czas temu tutaj. Warto zauważyć że kod w wielu miejscach jest niemal taki sam.

To jest wpis numer: 207

( . . . )

AutoCAD

Annotative dla opornych (1)

Dzisiejszy wpis jest pierwszym z kilku które zamierzam przedstawić, a dotyczących dynamicznych obiektów opisowych (annotative). Używanie obiektów opisowych pozwala na zautomatyzowane ich skalowanie – obiekty są dopasowywane do określonej skali. Do obiektu można przypisać wiele skal co umożliwia ich prawidłowe wyświetlanie w zależności od wybranej skali. Obiektami opisowymi są:

  • Tekst (jedno (TEXT) lub wielowierszowy (MTEXT)) i ich style (STYLE)
  • Definicje bloków i atrybutów (BLOCK / ATTDEF)
  • Kreskowania (HATCH)
  • Wymiary i style wymiarów (DIMENSION / DIMSTYLE)
  • Tolerancje geometryczne (TOLERANCE)
  • Wielolinie odniesienia i ich style (MLEADER / MLEADERSTYLE w słowniku ACAD_MLEADERSTYLE)

Pomimo tego że obiekty opisowe zostały wprowadzone do AutoCAD-a już dość dawno (w wersji 2008), wydaje się że nie są zbyt często stosowane przez użytkowników.

W kolejnych wpisach przedstawię zasady tworzenia, wykorzystania i sposoby manipulowania poszczególnymi obiektami opisowymi. Głownie z poziomu użytkownika, ale też zwrócę uwagę na programowy dostęp do obiektów opisowych LISP-em.

. . . )

To jest 206 wpis…

AutoLISP

TRUSTEDPATHS…

Jako wytrawny użytkownik AutoCAD-a… zapewne nie raz spotkałeś się z sytuacją którą zilustrowałem na poniższym (prostym acz ruchomym) obrazie:

Sytuacja ta związana jest z bezpieczeństwem i ma zapewnić zminimalizowaanie możliwości wczytania złośliwego kodu do AutoCAD-a. W wersji 2014 AutoCAD-a, wprowadzone zostały środki polegające między innymi na określeniu zaufanych lokalizacji. Zapisywane jest to w zmiennej systemowej o nazwie TRUSTEDPATHS.

Wygodnym sposobem na automatyczne ładowanie dowolnych aplikacji jest korzystanie z opcji polecenia APPLOAD (WCZYTAJAPL) zwanej Pakiet uruchomieniowy. Powoduje wczytanie określonych aplikacji przy każdym uruchomieniu AutoCAD-a:

Należy pamiętać o tym, aby aplikacje dodawane w pakiecie uruchomieniowym zawsze znajdowały się w zaufanych lokalizacjach. Tutaj znajdziesz więcej informacji o zmiennej TRUSTEDPATHS, a tu więcej o bezpieczeństwie AutoCAD-a.

Poniżej cztery funkcje LISP-owe, które napisałem aby zautomatyzować kontrolę i umożliwić manipulowanie wartością zmiennej TRUSTEDPATHS w dyskretny sposób z poziomu aplikacji. Pozwalają one odczytywać, porównywać, dodawać i usuwać, określone ścieżki dostępu. Umiejętne korzystanie z nich rozszerza możliwości programującego w sytuacjach automatycznego instalowania czy aktualizowania oprogramowania.

Zdefiniowane funkcje wymagają (rzecz jasna) wcześniejszego załadowania biblioteki CADPL-Pack, a wyglądają tak:


; ---------------------------------------------------------------------------- ;
; zwraca liste sciezek zmiennej TRUSTEDPATHS, jezeli [Mode] zwraca elementy    ;
; wielkimi literami                                                            ;
; ---------------------------------------------------------------------------- ;
(defun jk:SYS_GetTrustedPathsList (Mode / p r)
  (setq p (getvar "TRUSTEDPATHS")
        r (cd:STR_Parse p ";" nil)
  )
  (if Mode
    (mapcar 'strcase r)
    r
  )
)
; ---------------------------------------------------------------------------- ;
; zwraca T jezeli sciezka [Path] jest na liscie zmiennej TRUSTEDPATHS          ;
; ---------------------------------------------------------------------------- ;
(defun jk:SYS_TrustedPaths-p (Path / i p)
  (setq i (jk:SYS_GetTrustedPathsList t)
        p (strcase Path)
  )
  (not
    (zerop
      (length
        (member p i)
      )
    )
  )
)
; ---------------------------------------------------------------------------- ;
; dodaje sciezke [Path] do zmiennej TRUSTEDPATHS. zwraca T lub nil             ;
; ---------------------------------------------------------------------------- ;
(defun jk:SYS_TrustedPaths-Add (Path / p)
  (if
    (vl-file-directory-p Path)
    (progn
      (setq p (getvar "TRUSTEDPATHS"))
      (if
        (not (jk:SYS_TrustedPaths-p Path))
        (setvar "TRUSTEDPATHS" (strcat p ";" Path))
      )
      (jk:SYS_TrustedPaths-p Path)
    )
  )
)
; ---------------------------------------------------------------------------- ;
; usuwa sciezke [Path] ze zmiennej TRUSTEDPATHS. zwraca T lub nil              ;
; ---------------------------------------------------------------------------- ;
(defun jk:SYS_TrustedPaths-Del (Path / p)
  (setq p (jk:SYS_GetTrustedPathsList nil))
  (if
    (jk:SYS_TrustedPaths-p Path)
    (progn
      (setq p (vl-remove Path p)
            p (cd:STR_ReParse p ";")
      )
      (setvar "TRUSTEDPATHS" p)
      (not (jk:SYS_TrustedPaths-p Path))
    )
  )
)
; ---------------------------------------------------------------------------- ;

Poniżej przykład działania funkcji dodającej określoną ścieżkę do zmiennej TRUSTEDPATHS:

Po wykonaniu powyżej przedstawionego kodu, ostrzegawcze okno pokazane na początku nie pojawi podczas uruchomienia AutoCAD-a, a przykładowy plik zostanie załadowany.

Wpis ten tylko częściowo dotyka tematów związanych z możliwymi sposobami programowej zmiany środowiska AutoCAD-a. Wiedza na ten temat wydaje się być przeznaczona dla bardziej zaawansowanych użytkowników.

. . . )

To jest 205 wpis…

AutoLISP

FLATTEN inaczej (3) – poprawiona wersja

Pracując teraz nad nowym szybkim narzędziem służącym do oznaczania poziomów w modelu 3D, przy okazji, wróciłem do opisywanego już tutaj programu ChElev, którego poprawioną wersję właśnie przedstawiam. Samo działanie programu nie uległo zasadniczej zmianie. Zostały usunięte dwa główne błędy, dotyczące bloków z atrybutami, i wymiarów kątowych.

W obu przypadkach (mam nadzieję skutecznie) przypadłości owe zostały wyeliminowane.

Plik programu (wersja 1.30) znajduje się tutaj:: ChElev.fas (~10kB). Opis programu zaś we wcześniejszych wpisach: Flatten inaczej (2) i Flatten inaczej.

( . . . )

To jest 204 wpis…

AutoLISP, Interfejsy

Kolor tła ekranu (2)

(…) w modelu malujemy na czarnym, w papierze na… (właśnie) – na białym (?). Wszyscy (którzy mnie znają) wiedzą że jedynym słusznym kolorem obszaru papieru jest RGB 237,236,215

Możliwość zmiany koloru tła ekranu graficznego (osobno w modelu i osobno w papierze), to przydatna funkcja z kilku powodów. Po pierwsze długotrwała praca przed monitorem jest męcząca dla oczu, zaś (nie od dziś) wiadomo że ciemne tła są mniej uciążliwe dla wzroku. Po drugie, wykorzystywanie kolorów obiektów (czy kolorów warstw), które jest ułatwieniem podczas pracy na komputerze, różni się zwykle od prezentacji wyników, czyli ostatecznego wydruku, który w większości przypadków jest monochromatyczny.

Jasne kolory obiektów, na jasnym tle stają się słabo widoczne i odwrotnie ciemne (lub przygaszone) żle się prezentują na ciemnym tle.

Podobnie, gdy potrzebujemy przekazać na przykład zrzut ekranu, który być może będzie drukowany – wtedy kontrastowy obraz na jasnym tle będzie dużo lepiej przydatny niż standardowy czarny ekran AutoCAD-a.

Na swój użytek, zdefiniowałem (rzecz jasna w LISP-ie) polecenie BKG, które szybko zmienia kolor tła. Działa jak przełącznik, który sekwencyjnie zmienia stany. Polecenie w zależności od aktywnej przestrzeni zmienia tła. W modelu czarny / biały, a w obszarze papieru biały / RGB 237,236,215. Kod LISP-a wygląda tak:



; ------------------------------------------------------------------------ ;
; BKG - zmienia kolory tla AutoCAD-a w Modelu i obszarze papieru           ;
;       kojacek 2020/2021                                                  ;
; ------------------------------------------------------------------------ ;
(defun C:BKG ()(bkgcolor)(princ))
; ------------------------------------------------------------------------ ;
(defun bkgcolor (/ d c b m :i)
  (defun :i (m i / j x r)
    (setq j
      (if m
        '((255 255 255)(237 236 215))
        '((255 255 255)(0 0 0))
      )
    )
    (setq r
      (if
        (setq x (vl-position i j))
        (nth (abs (1- x)) j)
        (car j)
      )
    )
    (LM:RGB->OLE (car r)(cadr r)(caddr r))
  )
  (setq d (vla-get-display
            (vla-get-preferences
              (vlax-get-acad-object)
            )
          )
        m (= (getvar "CVPORT") 1)
        c (vlax-variant-value
            (vlax-variant-change-type
              (eval
              (list 
                (if m
                  vla-get-GraphicsWinLayoutBackgrndColor
                  vla-get-GraphicsWinModelBackgrndColor
                )
              d))
             vlax-vbLong
           )
         )
  )
  (if
    (zerop (getvar "TILEMODE"))
    (vla-put-GraphicsWinLayoutBackgrndColor
      d
      (:i t (LM:OLE->RGB c))
    )
    (vla-put-GraphicsWinModelBackgrndColor
      d
      (:i nil (LM:OLE->RGB c))
    )
  )
)
; ------------------------------------------------------------------------ ;
;; RGB -> OLE  -  Lee Mac                                                  ;
;; Args: r,g,b - [int] Red, Green, Blue values                             ;
(defun LM:RGB->OLE ( r g b )
    (logior (fix r) (lsh (fix g) 8) (lsh (fix b) 16))
)
;; OLE -> RGB  -  Lee Mac                                                  ;
;; Args: c - [int] OLE Colour                                              ;
(defun LM:OLE->RGB ( c )
    (mapcar '(lambda ( x ) (lsh (lsh (fix c) x) -24)) '(24 16 8))
)
; ------------------------------------------------------------------------ ;
;;; http://www.lee-mac.com/colourconversion.html                           ;
; ------------------------------------------------------------------------ ;
(princ)

Wykorzystuję tutaj dwie funkcje które stworzył Lee Mac, do konwersji kolorów OLE/RGB. Więcej znakomitych LISP-owych funkcji konwersji kolorów w AutoCAD-zie znajdziesz na stronie autora: http://www.lee-mac.com/colourconversion.html

O (nieco innych) zmianach koloru tła, pisałem (bardzo dawno temu) TUTAJ… (stąd 2 – w tytule dzisiejszym)

( . . . ) a to jest 203 wpis….

AutoLISP

Zmiana widoczności atrybutów bloku

Atrybuty bloków mają trzy tryby swojej widoczności: wszystkie są widoczne, lub wszystkie są niewidoczne albo – w trybie standard, niewidoczne są ukryte, a widoczne są widoczne. Odpowiada za to zmienna systemowa o nazwie ATTMODE. Do zmiany można użyć też polecenia ATRWID (_ATTDISP). Operacje te działają globalnie – dla wszystkich bloków z atrybutami w rysunku. Co zrobić gdy zechcemy (z różnyc powodów) sterować widocznością atrybutów dla tylko wybranych bloków, tak jak na (ruchomym) obrazie poniżej?

Pozostaje jedynie manipulacja programowa. Rzecz jasna LISP-owa. Poniżej kod definicji polecenia ATTH, które działa jak przełącznik radiowy – pokazuje lub ukrywa wszystkie atrybuty wskazanych bloków.


; ------------------------------------------------------------------------ ;
; Polecenie ATTH dla wskazanych blokow z atrybutami zmienia ich widocznosc ;
; by kojacek - 2015, 2021                                                  ;
; ------------------------------------------------------------------------ ;
(defun C:ATTH (/ s l %l %o %v)
  (princ "\nZmiana widoczności atrybutów bloku.")
  (if
    (setq s
      (ssget ":L"
        (list
          (cons 0 "INSERT")
          (cons 66 1)
          (cons 410 (getvar "CTAB"))
        )
      )
    )
    (progn
      (setq l (cd:SSX_Convert s 0))
      (cd:SYS_UndoBegin)
        (foreach % l
          (setq %l (cd:BLK_GetAttEntity %))
          (foreach %1 %l
            (setq %o (vlax-ename->vla-object %1)
                  %v (vla-get-visible %o)
            )
            (vla-put-visible
              %o
              (if
                (= %v :vlax-false)
                :vlax-true
                :vlax-false
              )
            )
          )
        )
      (cd:SYS_UndoEnd)
    )
    (princ "\nNic nie wybrano.")
  )
  (princ)
)
; ------------------------------------------------------------------------ ;

To stosunkowo proste narzędzie umożliwia sterowanie widocznością atrybutów wskazanych bloków w dwóch trybach: widoczne / ukryte. Wykorzystuje właściwość dostępną dla każdego obiektu graficznego o nazwie Visible. Rzecz jasna zmiana następuje dla obiektów typu ATTRIB (a nie) ATTDEF, czyli zmiany dotyczą tylko wybranych odniesień bloków. Makro do poprawnego działania wymaga załadowanego CADPL-Pack’a.

O zmianach widoczności obiektów w bloku (tu HATCH) pisałem też TU.

< . . . > To jest wpis numer 202

AutoLISP

Modne funkcje CHCEM i DEJ…

Dziś trochę nietypowy wpis. Dość krótki. O samym (technicznie) LISP-ie nie będzie. Ale (jednak) o pisaniu nawiasów coś wspomnę. I (nie zaczyna się od „i”) o stanie (lekkiego) poirytowania, dotykającego (czasami) stanu (pewnego (lecz (nadal) lekkiego)) wkurwienia… Można powiedzieć że sam sobie jestem winny. Wszak w wpisie stoi:

Chyba (jednak) wydaje się że inaczej (po tych dwóch stronach) to rozumiemy. Deklaracja pomocy (którą oferuję) nie polega na tym, że zrobię za ciebie wszystko (funkcja DEJ), bo czegoś potrzebujesz (funkcja CHCEM). Ja ((znów) jednak) oczekuję że coś sam zrobisz, a w miejscu gdzie jest problem, (a potrafię pomóc) pomogę. Kiedy coś Ci pokazuję, a później widzę, że nawet tego nie przeczytałeś… (naprawdę) mnie to wkurza (zmiękczenie). Nagminna ostatnio postawa roszczeniowa („To pomożesz czy nie?”), bez choćby cienia własnego zaangażowania, (prawdopodobnie) spowoduje niebawem zmianę formuły bloga. Myślę (coraz częściej) o zmianie w formie jednokierunkowej komunikacji. Czyli tylko publikacja wpisów (chcesz to czytaj), już bez „drugiego podskórnego życia”… (Nawiasem mówiąc) kusi mnie to coraz bardziej….

< . . . > To jest 201 wpis…

AutoLISP

DIMSTYLE Reactor

Krótko parę słów o pewnym reaktorze, którego działanie widać na poniższej animacji. Zmiana stylu wymiarowania (DIMSTYLE) powoduje automatyczna zmianę stylu tekstu (TEXTSTYLE), stylu tabeli (CTABLESTYLE) i stylu wielolinii odniesienia (CMLEADERSTYLE).

Działanie reaktora jest niezwykle proste – polega na zmianie odpowiednich zmiennych systemowych AutoCAD-a, według określonego wzorca. Tutaj (w prezentowanym przykładzie) wzorzec jest listą określoną w zmiennej globalnej LISP-a:

(setq *jk-DimStyleReactor*
  (list
    (list "Arch50"         "Arch-Dim"      "Arch50-Normal" "A"       )
    (list "Arch50-Inside"  "Arch-Dim"      "Arch50-Normal" "B"       )
    (list "Arch50-Outside" "Arch-Dim-Wide" "Arch50-Wide"   "C"       )
    (list "Standard"       "Standard"      "Standard"      "Standard")
  )
)

Kod reaktora wygląda tak:

; -------------------------------------------------------------------------------------- ;
; ch-sysvar - 2021 by kojacek - reaktor zmiennych systemowych (style)                    ;
; -------------------------------------------------------------------------------------- ;
(defun ch-sysvar (call callback / w y j e b a c   p i s)
  (setq j *jk-DimStyleReactor*)
  (if
    (= (strcase (car callback)) "DIMSTYLE")
    (progn
      (setq j
        (mapcar
         '(lambda (%)
            (mapcar 'strcase
              (list
                (car %)
                (cadr %)
                (caddr %)
                (cadddr %)
              )
            )
          ) j
        )
            i "Standard"
            s (mapcar 'strcase
                (cd:SYS_CollList "STYLE" 1)
              )
            y (mapcar 'strcase
                (cd:DCT_GetDictList
                  (cd:DCT_GetDict
                    (namedobjdict)
                    "ACAD_TABLESTYLE"
                  )
                  nil
                )
              )
            w (mapcar 'strcase
                (cd:DCT_GetDictList
                  (cd:DCT_GetDict
                    (namedobjdict)
                    "ACAD_MLEADERSTYLE"
                  )
                  nil
                )
              )
      )
      (if
        (member
          (setq e
            (strcase (getvar "DIMSTYLE"))
          )
          (mapcar 'car j)
        )
        (if
          (setq a (assoc e j))
          (progn
            (setq c (cadr a)
                  p (caddr a)
                  b (cadddr a)
            )
            (if
              (and
                (/= (strcase (getvar "TEXTSTYLE")) c)
                (tblobjname "STYLE" c)
              )
              (setvar "TEXTSTYLE" c)
              (setvar "TEXTSTYLE" i)
            )
            (if
              (and
                (/= (strcase (getvar "CMLEADERSTYLE")) p)
                (member p w)
              )
              (setvar "CMLEADERSTYLE" p)
              (setvar "CMLEADERSTYLE" i)
            )
            (if
              (and
                (/= (strcase (getvar "CTABLESTYLE")) b)
                (member b y)
              )
              (setvar "CTABLESTYLE" b)
              (setvar "CTABLESTYLE" i)
            )
          )
        )
        (progn
            (setvar "TEXTSTYLE" i)
            (setvar "CMLEADERSTYLE" i)
            (setvar "CTABLESTYLE" i)
        )
      )
    )
  )
)
; -------------------------------------------------------------------------------------- ;

Załadowanie (uruchomienie) reaktora wymaga wywołania:

(vlr-editor-reactor nil '((:VLR-sysVarChanged . ch-sysvar)))

Aby przykładowy reaktor działał, musi być załadowany CADPL-Pack. Oczywiście poprzez zmianę listy w zmiennej globalnej *jk-DimStyleReactor można tworzyć inne powiąznia między stylami. W prezentowanym przykładzie zakładam że wszystkie określone style są zdefiniowane w rysunku. O reaktorach już wcześniej pisałem tutaj, przy okazji „chowania” kreskowania podczas wymiarowania. O zmiennych globalnych pisałem też tutaj.

< . . . > To jest #200 wpis…

AutoCAD, AutoLISP

DWGPROPS

Polecenie DWGPROPS (DWGWŁAŚCIWOŚCI) ustawia i wyświetla właściwości pliku aktualnego rysunku. Wyświetla okno dialogowe z czterema zakładkami, z których jednej – o nazwie [Niestandardowe] – poświęcę wiecej uwagi.

Karta [Ogólne] zawiera wszystkie dane systemowe dotyczace pliku, takie jak typ, nazwa, lokalizacja, dane dotyczące zapisu/dostępu, czy atrybuty pliku. Tych danych nie można samodzielnie modyfikować. W karcie [Podsumowanie] można uzupełnić pewne typowe dane, takie jak: tytuł, temat, czy słowa kluczowe. Dane te mogą być pomocne podczas przeszukiwania archiwum plików. Karta [Statystyka] (tylko do odczytu) zawiera pewne dane statystyczne dotyczące pliku (nazwę autora, czas edycji, wersję). Wreszcie, ostatnia (najbardziej nas interesująca) karta – [Niestandardowe]…

Pomysłów na wykorzystanie tych danych jest kilka – przedstawię dwa, z których (czasem) korzystam.

  1. Miejsce zapisu ustawień aplikacji. Dane w DWGPROPS wydają się być idealnym miejscem na przechowywanie pewnych ustawień aplikacji (np. LISP). Pozwalają bowiem użytkownikowi na modyfikację tych danych, bez konieczności tworzenia specjalnego interfejsu zmiany ustawień. Przykładem takiego zastosowania jest przedstawiony tutaj program (zarówno w wersji 1 jak i 2). Dwa parametry ustawień programu, których początkowe wartości, ustala się w trakcie działania programu, zapisywane są we niestandardowych właściwościach pliku: Wartość o nazwie UWG, odpowiada za ustalenie 5, 6, 7 albo 8 strefy ukłdu współrzędnych geograficznych (zobacz). Drugi zaś o nazwie DEC, odpowiada za ilość miejsc dziesiętnych zapisu współrzędnych, jeśli zapisywane są jako wartości liczbowe (to wersja 2 programu). Rzecz jasna oprogramowanie musi zapewnić kontrolę poprawności tych danych.
  2. Przechowywanie danych tekstowych wspólnych dla projektu składającego się z wielu plików.  Są to informacje zwykle występujące w tabliczkach rysunkowych (w arkuszach), a zawierające powtarzalne, wspólne dla wielu plików dane. Ten sposób jest łatwy do modyfikacji, aktualizacji i stosunkowo szybki w użyciu. Pomysłem na takie zastosowanie jest, w pierwszej kolejności użycie pliku tekstowego (np. standardowego pliku ini). Dane w nim się znajdujace mogą być teraz wczytane kolejno (a) do DWGPROPS: a następnie (b) do bloków tabliczek rysunkowych, jako zwykłe wartości atrybutów: albo (bardziej elastyczny sposób), jako pola danych (czyli obiekty typu FIELD): Ten sposób pozwala na samoaktualizację danych w tabliczkch rysynkowych (czy też innych blokach), po każdorazowej zmianie danych w DWGPROPS. Zmiany te mogą być wprowadzane ręcznie, lub w sposób automatyczny przez oprogramowanie.

Oczywiście sposobów na sprytne wykorzystywanie danych DWGPROPS, może być więcej, zwłaszcza że, mogą być to informacje bardzo zindywidualizowane – przedstawiłem na prostych przykładach jedynie zarys możliwości. „Wytrawni” programiści mogą pokusić się o stworzenie narzędzi, zapisujących, odczytujacych, aktualizujacych, usuwających, dowolne tekstowe dane w DWGPROPS, dla wielu różnych zastosowań, pojedyńczo czy też masowo dla wielu plików.

Omawiając DWGPROPS, wspomnę jeszcze o dwóch rzeczch: dane te mogą być przetwarzane przez standardowe polecenie AutoCAD-a do wyodrębniania danych (DATAEXTRACTION), jak też istnieje polecenie z ExpressTool, o nazwie PROPULATE pozwalające w pewien (ale dość ograniczony) sposób manipulować tymi danymi.

Na koniec – sprawdźmy „pojemność” niestandardowych danych w DWGPROPS. Poniższy kod (wykorzystujący funkcje z CADPL-Pack‚a):dodaje 1000 nowych wpisów:

(  .  .  . )

To jest 199 wpis (czwarty w tym roku).

AutoLISP

Wpółrzędne geograficzne LISP-em – wersja 2

Opisywanie map współrzędnymi geograficznymi które przedstawiłem we wpisie w lipcu 2020 roku, spotkało się z takim zainteresowaniem, że koniecznie stało się napisanie rozszerzenia programu. Zatem dziś wersja druga tegoż. Niejedyna (ale) najważniejsza zmiana, to dodanie funkcjonalności polegającej na, mozliwości wyboru formatu opisu. W dotychczasowej (i teraz domyślnie) formatem zapisu jest użycie stopni/minut/sekund. W nowej wersji dodana została możliwość zapisu współrzędnych geograficznych w formie liczby dziesiętnej:

Ustawień dokonuje się w oknie dialogowym, wyświetlanym podczas pierwszego wywołania programu („zaptaszając” wybranie formatu dziesiętnego):

Domyślną dokładnością (ilością miejsc po przecinku)  jest liczba z 4 znakami po przecinku. Program wykorzystuje ten sam sposób obliczania współrzędnych, zmienia się jedynie format ich przedstawienia.

Polecenie: WGEO                                            Plik programu do pobrania: WGeo-PL2k_v2.fas (~10kB)

.  .  . )

To jest 198 wpis (trzeci w tym roku).