AutoLISP

ATTA – Edycja atrybutu dla wielu bloków

Ponieważ dotarły (różnymi drogami) do mnie (nieco) krytyczne głosy dotyczące zbyt małej ilości nawiasów prezentowanych ostatnio na blog-u… zatem czym prędzej staram się coś temu zaradzić… Krótki (ale użyteczny) kawałek kodu zaprezentowany poniżej, służy do edycji atrybutów w wielu blokach jednocześnie. Jakie ma to zastosowanie?   

Dobrze skonstruowany projekt zawiera definicję (dwu lub trójwymiarową)  dowolnego obiektu w obszarze modelu, na podstawie którego tworzona jest dokumentacja rysunkowa, składająca się z co najmniej jednego lub więcej arkuszy, tworzonych w obszarze papieru. Wykorzystuje się do tego tak zwane Layout-y, (układy) czy też w innej nomenklaturze znane jako zakładki. Zwykle tworzą one „obszar papieru” czyli pojedyncze arkusze (dwuwymiarowe rysunki) dokumentacji technicznej. Jedną z najważniejszych rzeczy opisujących  rysunek są (między innymi) tabliczki rysunkowe. Znów – w dobrze skonstruowanym projekcie, większość zawartych w nich informacji tworzona jest automatycznie na podstawie danych zgromadzonych w jednym miejscu, a wspólnych dla całości projektu.  

Dobrym zwyczajem jest stosowanie w tym przypadku pól danych (FIELD-ów), ze względu na możliwość ich automatycznej aktualizacji. Są to informacje wspólne dla całości projektu. Część danych (tutaj trzech atrybutów w bloku tabliczki rysunkowej), z różnych względów, musi być jednak edytowana ręcznie. Oczywiście nie wyklucza to też automatyzacji tego procesu. 

Poniżej prezentuję zapowiadany krótki kod umożliwiający edycję wartości jednego atrybutu dla wszystkich (występujących w pliku) wstawień tego samego bloku. Zdefiniowane jest polecenie ATTA, i wymaga ono tylko wskazania dowolnego edytowalnego atrybutu bloku. Zmiana wartości wybranego atrybutu dokonywana jest we wszystkich wstawieniach tego bloku.  


; ------------------------------------------------------------------ ;
; ATTA by kojacek 2018 - Multiple blocks edit attribute              ;
; ------------------------------------------------------------------ ;
(defun C:ATTA (/ e d b s l i a v r)
  (if
    (and
      (setq e (nentsel "\nSelect an attribute: "))
      (= 2 (length e))
      (= "ATTRIB" (cdr (assoc 0 (setq d (entget (car e))))))
    )
    (progn
      (setq b (cdr (assoc 2 (entget (cdr (assoc 330 d))))))
      (if
        (setq s (ssget "_x" (list (cons 0 "INSERT")(cons 2 b))))
        (progn
          (setq l (cd:SSX_Convert s 0)
                i (length l)
                a (cdr (assoc 2 d))
                v (cdr (assoc 1 d))
          )
          (if
            (setq r
              (cd:DCL_StdEditBoxDialog
                (list 0 0 v)
                (strcat "Edit Attribute (in " (itoa i) " blocks)")
                (strcat "Block: " (strcase b) " / Tag: " (strcase a))
                50 10 (list "&OK" "&Cancel") T 50)
            )
            (progn
              (cd:SYS_UndoBegin)
              (foreach % l
                (cd:BLK_SetAttValueVLA % a r)
              )
              (princ
                (strcat "\nUpdated " (itoa i) " blocks. ")
              )
              (cd:SYS_UndoEnd)
            )
            (princ "\nCanceled. ")
          )
        )
      )
    )
    (princ "\nInvalid object selected. ")
  )
  (princ)
)
; ------------------------------------------------------------------ ;
(princ)

Jak widać, zmiana wartości trzech atrybutów w dowolnej ilości bloków trwa zaledwie tylko chwilę. Tutaj (w prezentowanym przykładzie) jest ich 27. Wyobraźmy sobie zmianę tych wartości w kolejno występujących 27 tabliczkach w 27 layout-ach… 

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

( . . . )

Reklamy
AutoLISP

CenterMark w obszarze papieru

Modelowanie 3D to rzecz jasna wielkie ułatwienie w procesie projektowania. Pozwala ono (między innymi) w stosunkowo szybki sposób tworzyć bezbłędną trójwymiarową reprezentację dowolnej konstrukcji. Zanim jednak stanie się ona rzeczywistym bytem, prędzej czy później (w większości przypadków) trzeba ją odwzorować w dokumentacji dwuwymiarowej. Ta zaś musi spełniać wiele (unormowanych i sformalizowanych) wymogów dotyczących opisu geometrii przedmiotu. Jednym z nich jest wymiarowanie, a na te oprócz samych wymiarów, składają się także „nierzeczywiste” obiekty jakimi są osie symetrii i znaczniki środków. W AutoCAD-zie, (zwykle) tworzy się model (dwu lub trójwymiarowy) w przestrzeni modelu (ModelSpace), zaś jego dwuwymiarową projekcję i część dokumentacyjną (czyli właściwy rysunek techniczny) z wymiarami, opisami, zestawieniami, tabliczkami itp. w obszarze papieru (PaperSpace).
Powyższa animacja ilustruje działanie mojego krótkiego LISP-owego makra, tworzącego znaczniki środka w obszarze papieru, dla rzutni obiektów trójwymiarowych utworzonych w przestrzeni modelu. W znaczący sposób ułatwia i przyspiesza ono tworzenie końcowej dokumentacji. Zdefiniowane polecenie CC, po wskazaniu punktu, będącego środkiem znacznika środka, pozwala w dynamiczny sposób, poprzez określenie drugiego punktu, ustalić jego wielkość. Ponadto umożliwia stosowanie znacznika nie tylko dla obiektów typu okrąg i łuk, lecz wstawia go w dowolnym miejscu. Dodatkowo umożliwia wielokrotne wstawianie znacznika w innych punktach, jako kopię ostatnio zdefiniowanego. To działanie przyspiesza pracę, w sytuacji oznaczania wielu otworów tej samej wielkości. Możliwości te są niedostępne dla standardowego znacznika środka tworzonego poleceniem ZNACZNIKŚRODKA (_CENTERMARK). Jedyną wadą stosowania tego rozwiązania jest utrata wiązania znacznika środka z opisywanym obiektem. W zdecydowanej większości przypadków, zachowanie tego połączenia nie ma znaczenia – geometria ta, nigdy nie ulega już zmianie.

.

O obiektach typu CenterMark i CenterLine pisałem (nawet dokładnie) w przeszłości już TUTAJ, a o sposobach rozpoznawania bloków (jakimi w rzeczywistości są te obiekty) poświęciłem TEN wpis.

  .

( . . . )
AutoCAD, AutoLISP

AutoCAD 3D…

Czy AutoCAD nadaje się do modelowania 3D? Uprzedzając głosy sprzeciwu wszystkich codziennie modelujących, w programach za tysiące dolarów, gładkie powierzchnie n-tego stopnia… odpowiem – w pewnym stopniu – tak. W pewnym stopniu, oznacza także w ograniczonym stopniu. Czego bowiem nie powiedzieć, możliwości AutoCAD-a, w modelowaniu trójwymiarowym, nie są szczególnie wyszukane. Tym wpisem rozpocznę serię kilku omówień zagadnień modelowania 3D w AutoCAD-zie, z uwzględnieniem (oczywiście) jednego z narzędzi API – LISP-a.

Pewnym, (ale) niewątpliwie niebagatelnym problemem stosowania „surowego” trójwymiarowego modelowania w AutoCAD-zie, jest w dość szerokim stopniu ograniczony dostęp do informacji ma temat geometrii bryły. W pewnym zakresie specjalizowane programy oparte o AutoCAD-a (AutoCAD Architecture, AutoCAD Mechanical, AutoCAD Plant…), omijają te problemy, wykorzystując własne obiekty 3D (ściany, okna, drzwi, otwory, belki, słupy, ławy, skarpy itp.). Mają one własne specjalnie im dedykowane właściwości, zawierające większość potrzebnych specyficznych danych.

Używając tylko podstawowego AutoCAD-owego modelera 3D, trzeba liczyć się z dość szerokimi ograniczeniami. Na swój użytek, wykorzystuję narzędzie LISP-owe które napisałem w celu pobierania głównych gabarytów wskazanych obiektów 3D. Działanie tego narzędzia widać na poniższej animacji.

Zadaniem jest określenie gabarytów wskazanej bryły. Jak widać jedynie w przypadku obiektu typu 3DSOLID (box) możliwe jest pobranie pełnych informacji. Dla obiektów utworzonych przez wyciągnięcie (extrude – ale ważny jest typ wyciąganych elementów), można uzyskać jedynie wielkość wyciągnięcia. Dla innych (utworzonych w inny sposób, lub edytowanych przez ucięcie, sumę, różnicę itp. informacje te są całkowicie niedostępne. Wiedza ta, przydaje się w samym procesie modelowania. Bowiem, do osiągnięcia tego samego efektu, można  dojść różnymi drogami. Niektóre z nich mogą jednak ograniczać dostęp do pewnych podstawowych danych. Tak samo mogą zamykać drogę dalszej łatwej edycji obiektów.

W jakim celu, oraz w jaki sposób uzyskuję dostęp do tych informacji (i nie tylko), postaram się opisać w (niedalekiej) przyszłości…

. . .  )

AutoLISP

Jak ważne są wybory…

… niestety (czasem / coraz częściej) przekonujemy się zbyt (za (?)) późno…  O wyborach obiektów parę słów zatem. AutoCAD dla swoich poleceń oferuje dwie możliwości wyboru obiektów: po wywołaniu polecenia (to naturalny standard) oraz (dodatkowo) przed jego wywołaniem. Oczywiście ta druga opcja jest najbardziej elastyczna, bowiem oferuje użytkownikowi możliwość wcześniejszego wyboru obiektów, a potem zdecydowanie co zrobić z tym wyborem. Steruje tym zmienna systemowa o nazwie PICKFIRST. Ma ona domyślnie wartość 1 (umożliwia wybór obiektów przed uruchomieniem polecenia), lub 0 (obiekty mogą być wybierane po wywołaniu polecenia).

Powyższe rozważania mają zastosowanie nie tylko dla standardowych poleceń AutoCAD-a, ale również poleceń tworzonych przez dowolne API, w tym (co dla nas najważniejsze), definiowanych w AutoLISP-ie. Istnieje bowiem funkcja LISP-owa o nazwie ssgetfirst, która umożliwia stworzenie zbioru wskazań, dla obiektów zaznaczonych uchwytami. Funkcja zwraca dwuelementową listę, której pierwszy element ma zawsze wartość nil (uwarunkowania historyczne), a drugi jest zbiorem wskazań zaznaczonych (uchwytami) obiektów. Gdy nie ma takich obiektów funkcja zwraca listę w formie:

Mechanizm ten (gdy drugi element listy nie jest nil) pozwala, programującemu, na stworzenie takiego swojego polecenia, które będzie działało tak samo jak wszystkie standardowe polecenia AutoCAD-a. Czyli (gdy PICKFIRST = 1), polecenie działa na obiektach wybranych zarówno przed jak i po jego wywołaniu. Pomocna w tym będzie (krótka) funkcja:


(defun jk:SSX_ActiveSel ()
  (if
    (not (zerop (getvar "PICKFIRST")))
    (cadr (ssgetfirst))
  )
)

Zwraca ona (przy jednoczesnej pozytywnej wartości PICKFIRST) zbiór wskazań jeśli obiekty zostały zaznaczone, lub nil w przeciwnym wypadku. Działanie funkcji ilustruje poniższy (ruchomy) obraz:

Przyznam że niezbyt często zwracam uwagę w konstruowaniu poleceń, aby uwzględniały one wcześniejszy zbiór wskazań. Powodem jest dedykowanie działań dla ściśle określonych wyborów, zwykle zawężonych. Stąd dokonywanie wyborów (zbiorów wskazań) wymagane jest po wywołaniu polecenia. Rzecz jasna dla operacji ogólnych dla wszystkich dowolnych obiektów (usuwanie, zmiany cech itp.), warto stosować opisaną powyżej konstrukcję. Wygląda profesjonalnie i (co najważniejsze) zachowuje się tak jak każde standardowe polecenia AutoCAD-a. To ważna cecha, ceniona przez użytkowników.

.  .  .  )

AutoCAD, AutoLISP

Obiekty nazwane

O nazwach, czyli co wolno a czego nie wolno używać w nazwach obiektów. Z pewnością nie raz, mogliście spotkać się z takim (nieco smutnym) komunikatem Menedżera warstw, zabijającym wasze mozolne (i niewątpliwie kreatywne) starania w celu utworzenia tej jedynej niepowtarzalnej nazwy warstwy… 😉

O nazwanych (autocad-owych)  obiektach pisałem już TUTAJ, a o pewnych możliwościach ich zmiany także – TUTAJ. Dziś chcę przybliżyć nieco problemy związane z podaniem poprawnej nazwy w LISP-ie. O ile bowiem, ręczne działania użytkownika skutkują wyświetlaniem odpowiednich (jak powyżej) komunikatów, to działania w (dowolnym) API, z niedopuszczalną nazwą, powodują zawsze błąd programu. Ponieważ (zaś) nadrzędnym zadaniem programującego jest zapewnienie poprawnego działania programu w możliwie najszerszym zakresie, ważnym jest przewidywanie potencjalnych błędów i zapobieganie im, już na najniższym poziomie.

Obecnie (od wersji AutoCAD 2000 (1999)) w nazwach symboli nazwanych niedopuszczalne są znaki:

Oczywiście AutoCAD zapewnia pełną zgodność z wcześniejszymi, wprowadzając odpowiednią zmienną systemową:

W AutoLISP istnieje funkcja o nazwie snvalid, która  pozwala sprawdzić czy nazwa określona jako jej argument, jest dopuszczalną nazwą symbolu. W niektórych jednak sytuacjach, okazuje się to niewystarczające. Stąd też zdefiniowałem swoją LISP-ową funkcję którą wywołuję w kodzie, gdy snvalid, zwraca wartość NIL. W tym przypadku, funkcja jk:STR_SNValid, dokonuje „naprawy” nazwy (argument Str) , zastępując wszystkie wystąpienia znaków niedopuszczalnych, znakiem określonym jako argument Nst. Funkcja wygląda tak:


;;; --------------------------------------------------------------- ;;;
;;; jk:STR_SNValid - kojacek 2018                                   ;;;
;;; --------------------------------------------------------------- ;;;
(defun jk:STR_SNValid (Str Nst / p)
  (setq p  (list 34 42 44 47 58 59 60 61 62 63 96 92 124))
  (while p
    (setq Str (vl-string-translate (chr (car p)) Nst Str)
          p (cdr p)
    )
  )
  Str
)
;;; --------------------------------------------------------------- ;;;

Przykładowe wywołanie (i wynik działania) ilustruje obraz:

Potrzeba napisania tej funkcji powstała w wyniku użycia pewnego (doskonałego zresztą) programu zmieniającego wstawienie bloku dynamicznego na odpowiadający mu graficznie blok statyczny. Program ów, jako część nowej nazwy bloku przyjmował opis jego stanu widoczności. W niektórych blokach opis ten zawierał znaki, które są niedopuszczalne, w nazwie bloku, w konsekwencji czego, program generował błąd. Użycie funkcji jk:STR_SNValid, skutecznie wyeliminowało przyczynę powstawania błędu, i odtąd program działa bez zakłóceń.

. . . )

AutoLISP

WeldLine

Pomysł na zastosowanie złożonego rodzaju linii jako linii symbolizującej oznaczenie spoiny, zrodził się (i był zrealizowany) już wiele lat temu. Teraz (od czasu do czasu) znów się przydaje, już w nieco unowocześnionej i odmłodzonej formie, w zupełnie innym miejscu…

Mechanizm dynamicznego tworzenia złożonego rodzaju linii, jeśli nie istnieje on w rysunku, opisywałem już przy okazji rysowania izolacji.  W identyczny sposób postępuję również w tym przypadku, różnicą jest jedynie definicja rodzaju linii. W przeciwieństwie do linii izolacji, gdzie wykorzystywany jest symbol, tutaj elementem linii jest tekst. To powoduje potrzebę również dynamicznego utworzenia stylu tekstu, przed wczytaniem definicji linii. Wszystkie te działania odbywają się „w locie”, co czyni to narzędzie prostym i wydajnym w użyciu,  ograniczając działanie rysującego do niezbędnego minimum. A o to przecież w cad-owskich zabawach właśnie chodzi 😉 …

Stosunkowo krótki (dzięki CADPL-Pack‚owej optymalizacji) przedstawiam poniżej:


;;; --------------------------------------------------------------- ;;;
;;; WeldLine.lsp                                                    ;;;
;;; by kojacek 2000, 2018                                           ;;;
;;; --------------------------------------------------------------- ;;;
(setq *jk-WELD* 3.0)
;;; --------------------------------------------------------------- ;;;
(defun C:WELDLINE ()(jk:StdSteel-WeldLine)(princ))
;;; --------------------------------------------------------------- ;;;
(defun jk:StdSteel-WeldLine (/ -%l s e l p c f o)
  (defun -%l (/ l f s c)
    (if
      (not (tblobjname "LTYPE" "WeldLine"))
      (progn
        (if
          (not (tblobjname "STYLE" "Std-Weld-Symbol"))
          (progn
            (setq s (cd:ACX_AddTextStyle "Std-Weld-Symbol"))
            (if
              (setq c (findfile "simplex.shx"))
              (progn
                (vla-put-Fontfile s c)
                (vla-put-Height s 0.0)
              )
            )
          )
        )
        (setq l
          (list
            "*WeldLine,Weld Line ((((((((((((((("
            (strcat
              "A,0,-0.2,[\"(\",Std-Weld-Symbol,S=0.65,"
              "R=0.0,X=0,Y=0.22],-0.4"
            )
          )
              f (vl-filename-mktemp nil nil ".lin")
        )
        (cd:SYS_WriteFile f l nil)
        (vla-load (cd:ACX_LineTypes) "WeldLine" f)
        (vl-file-delete f)
      )
    )
    (or (tblobjname "LTYPE" "WeldLine"))
  )
  (setq c
    (getreal
      (strcat
        "\nHeight of weld line <"
        (cd:CON_Real2Str *jk-WELD* 2 nil) ">:"
      )
    )
  )
  (if (not c)(setq c *jk-WELD*))
  (cd:SYS_UndoBegin)
  (if
    (setq l (-%l))
    (progn
      (setq *jk-WELD* c)
      (setq s (getpoint "\nStart point: "))
      (if
        (setq e (getpoint s "\nEnd point: "))
        (progn
          (setq o
            (cd:ACX_AddLWPolyline
              (cd:ACX_ASpace)(list s e) nil)
          )
          (vla-put-LineType o "WeldLine")
          (vla-put-LinetypeScale o c)
          (vla-put-Color o 8)
          (vla-put-LineWeight o 0.0)
        )
        (princ "\n*** Error - Invalid point.")
      )
    )
    (princ "\n*** Error - Linetype not loaded.")
  )
  (cd:SYS_UndoEnd)
  (princ)
)
;;; -------------------------------------------------------------- ;;;
(princ)

„Stara” (teraz już pełnoletnia) wersja rysowania spoiny była wspomniana na cad.pl tutaj

( . . . )

AutoLISP

Zmienna SNAPANG

Zmienna systemowa SNAPANG służy do ustala kąta obrotu pomocniczej siatki lokalizacyjnej dla bieżącej rzutni względem aktualnego układu współrzędnych. Wartość kąta można podać w linii poleceń, lub ustalić przez wskazanie dwóch punktów. W połączeniu z włączonym trybem ortogonalnym (zmienna ORTHOMODE), stanowi doskonałe narzędzie do precyzyjnego rysowania, bez konieczności manipulacji układem współrzędnych. Do szybkiego i bezbłędnego ustawienia zmiennej napisałem LISP-owe polecenie X, które dla określonych wskazanych entycji pobiera ich orientację na płaszczyźnie, i na tej podstawie ustala jej nową wartość. Obiektami rysunkowymi są: prosta, półprosta, linia, odniesienie do bloku (również XRef), wielokrotne wstawienie bloku, definicja atrybutu, tekst, tekst wielowierszowy, oraz lekka polilinia. W tym przypadku kąt określany jest na podstawie właściwości segmentu przez który została wybrana polilinia. Brak wyboru, czyli wskazanie punktu (nie obiektu) ustawia zmienną SNAPANG na wartość 0.0.

Działanie polecenia X widać poniżej, na przykładzie ustawienia SNAPANG do wskazanego bloku:

Poniżej zaś cały kod definicji polecenia:


; =============================================================== ;
; snapang.lsp                                                     ;
; Ustawia zmienna SNAPANG do wskazanego obiektu                   ;
; by kojacek 2010,2018                                            ;
; =============================================================== ;
(defun C:X (/ s e n d _rtd _gap)
  (defun _rtd (r)(* 180.0 (/ r pi)))
  (defun _gap (es / s p)
    (if
      (setq s (jk:ENT_GetLWPolySeg (car es)(cadr es)))
      (progn
        (setq p (jk:ENT_GetLWPolySegPoints (car es) s))
        (angle (car p)(cadr p))
      )
    )
  )
  (cd:SYS_UndoBegin)
  (if
    (setq s (entsel "\nSelect object: ") e (car s))
    (progn
      (setq d (entget e) n (cdr (assoc 0 d)))
      (setvar "SNAPANG"
        (cond
          ( (= "LINE" n)
            (angle (cdr (assoc 10 d))(cdr (assoc 11 d)))
          )
          ( (= "LWPOLYLINE" n)(_gap s))
          ( (member n
              '("TEXT" "MTEXT" "INSERT" "MINSERT"
                "ATTRIB" "ATTDEF"
              )
            )
            (cdr (assoc 50 d))
          )
          ( (member n '("RAY" "XLINE"))
            (angle '(0.0 0.0 0.0)(cdr (assoc 11 d)))
          )
          (t 0.0)
        )
      )
    )
    (setvar "SNAPANG" 0.0)
  )
  (cd:SYS_UndoEnd)
  (princ
    (strcat "\nSNAPANG = "
      (vl-princ-to-string (_rtd (getvar "SNAPANG")))
    )
  )
  (princ)
)
; =============================================================== ;
; 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)
    )
  )
)
; =============================================================== ;
; Zwraca liste wspolrzednych segmentu SE dla LWPOLYLINE EN        ;
; =============================================================== ;
(defun jk:ENT_GetLWPolySegPoints (en se / v)
  (setq v (jk:ENT_LWPolySegs en nil))
  (if
    (< se v)
    (list
      (vlax-curve-getPointAtParam en se)
      (vlax-curve-getPointAtParam en (1+ se))
    )
  )
)
; =============================================================== ;
; zwraca ilosc segmentow LWPOLYLINE (gdy Mode=Nil) lub liste nume-;
; row segmentow: (0 1 2 ... N)                                    ;
; =============================================================== ;
(defun jk:ENT_LWPolySegs (en mode / v i l)
  (setq v (cdr (assoc 90 (entget en)))
        v (if
            (vlax-curve-isClosed en)
            v
            (1- v)
          )
  )
  (if Mode
    (progn
      (setq i 0)
      (while
        (< (length l)(1- v))
        (setq i (1+ i))
        (setq l (append (list i) l))
      )
      (cons 0 (reverse l))
    )
    v
  )
)
; =============================================================== ;
(princ)

Rzecz jasna konieczne jest wcześniejsze załadowanie biblioteki CADPL-Pack. O ile ustawienie zmiennej SNAPANG do obiektów typu LINE, XLINE, TEXT, MTEXT czy INSERT, jest stosunkowo proste, większym wyzwaniem było wyznaczenie kąta segmentu przez który została wskazana polilinia. Stąd obecność kilku bibliotecznych funkcji manipulowania na obiektach typu LWPOLYLINE.  Wykorzystuję tutaj pobranie numeru segmentu wskazanej polilinii (funkcja jk:ENT_GetLWPolySeg oraz pomocniczo jk:ENT_LWPolySegs) na podstawie punktu wskazania, następnie wybranie jego współrzędnych (funkcja jk:ENT_GetLWPolySegPoints) która w końcu służy do wyliczenie kąta segmentu. Polecenie X, można rzecz jasna rozbudować o  możliwość ustawienia SNAPANG, na podstawie kąta zdefiniowanego przez inne obiekty rysunkowe.

( . . . )