Chuyển đến nội dung
Diễn đàn CADViet
ssg

Nghệ thuật lập trình Lisp

Các bài được khuyến nghị

Cùng với sự phát triển của CadViet, sự quan tâm của các member đối với lisp ngày càng tăng. Mọi người đều thấy rất rõ rằng, lisp là phương tiện hữu hiệu để nâng cao năng suất và hiệu quả sử dụng AutoCAD. Là người dẫn chương trình của diễn đàn Lisp & VBA, ssg rất vui khi số lượng, chất lượng cũng như sự nhiệt tình của đội ngũ lập trình viên lisp của CadViet phát triển không ngừng.

Ssg lập topic này như là nơi giao lưu, chia sẻ, trao đổi kinh nghiệm và học hỏi lẫn nhau cho tất cả những ai quan tâm đến “cái gọi là nghệ thuật lập trình Lisp.

Ssg xin mở đầu chuyên mục này bằng một bài phân tích, dựa trên yêu cầu của bạn vinataba vừa mới post trên diễn đàn: Tính và ghi kết quả giá trị trung bình của các đối tượng text.

Một bài toán xem ra rất đơn giản, hầu như ai biết chút ít về lisp cũng có thể làm được. Nhưng đằng sau cái đơn giản ấy có nhiều điều đáng được đưa ra xem xét…

Trước hết là code của ssg:

 

;;;---------------------------------------------------------
(defun aver(L / tot x) ;;;Calc average from list of reals
(setq tot 0.0)
(foreach x L (setq tot (+ tot x)))
(if L (/ tot (length L)))
)
;;;---------------------------------------------------------
(defun wtxt(txt p / sty d h1 h2 wf h) ;;;Write txt on graphic screen at p
(setq
   sty (getvar "textstyle")
   d (tblsearch "style" sty)
   h1 (cdr (assoc 40 d))
   h2 (cdr (assoc 42 d))
   wf (cdr (assoc 41 d))
)
(if (> h1 0) (setq h h1) (setq h h2))
(entmake (list (cons 0 "TEXT") (cons 7 sty) (cons 40 h) (cons 41 wf) (cons 1 txt) (cons 10 p)))
)
;;;---------------------------------------------------------
(defun C:AVE( / ss p L e v)
;;;SELECT OBJECTS
(setq
   ss (ssget '((0 . "TEXT")))
   p (getpoint "\nBase point:")
   L nil
)
;;;FILTER OBJECTS
(while (setq e (ssname ss 0))
   (setq v (cdr (assoc 1 (entget e))))
   (if (= (atof v) (read v)) (setq L (append L (list (atof v)))))
   (ssdel e ss)
)
;;;CALC AND WRITE RESULT
(wtxt (rtos (aver L)) p)
(princ)
)
;;;---------------------------------------------------------

 

Và tiếp theo là một số ý kiến:

 

1) Tính cấu trúc của chương trình

Hầu như tất cả các tài liệu hướng dẫn về lập trình đều nhấn rất mạnh điều này. Chương trình được lập với cấu trúc logic, chặt chẽ sẽ tạo điều kiện rất thuận lợi cho chính bản thân người lập trình: dễ viết, dễ kiểm lỗi, dễ có cái nhìn xuyên suốt toàn bộ chương trình, dễ kế thừa, vận dụng kết quả đạt được cho các chương trình khác, v.v… và v.v…

Ssg không phân tích thêm, nhưng có một sự so sánh đơn giản: viết chương trình có lẽ cũng giống như viết văn! Mình chẳng phải nhà văn nhưng ngày xưa đi học, làm bài luận văn vẫn thường theo các bước:

- Đọc và phân tích kỹ đề bài

- Lập dàn ý bằng các gạch đầu dòng

- Phát triển từng cái gạch đầu dòng, thêm… mắm muối gia vị vào là thành bài văn!

Kết quả cho thấy, bài nào mình càng chịu khó lập kỹ dàn ý thì càng được điểm cao.

Với đề bài này, căn cứ vào công việc phải làm của chương trình, dàn ý được lập như sau (thể hiện rất rõ trong chương trình chính C:AVE):

- Chọn đối tượng: dùng ssget. Thực chất cũng đã kết hợp “chọn” và “lọc” nhưng là “lọc thô”, vì hàm ssget không có tùy chọn nào cho phép phân biệt được text chứa số và text chứa chữ.

- Lọc đối tượng: loại ra các text có chứa chữ. Có nhiều cách để đạt được mục đích này. Ở đây dựa vào hàm read, một đối tượng text không chứa chữ thì kết quả hàm read và hàm atof bằng nhau.

- Tính giá trị trung bình: sau khi đã chọn và lọc, kết quả ta thu được chắc chắn là một list chỉ chứa toàn các số thực. Hàm aver không phải “bận tâm” đến việc check dữ liệu nữa, nó chỉ làm công việc đơn giản là gán các biểu thức toán học thuần túy.

- Ghi kết quả: có vẻ như là công việc dễ nhất, nhưng có một chút rắc rối, sẽ phân tích kỹ hơn ở mục 3.

 

2) Sự tích lũy tài nguyên

Chương trình được lập ra trước tiên là để đáp ứng một yêu cầu cụ thể (người nêu yêu cầu có lẽ sẽ hài lòng với kết quả này). Nhưng ngoài điều đó ra, chúng ta còn thu được những gì khác nữa hay không? Cái quan trọng nhất đối với lập trình viên chính là các public functions, mà cụ thể ở đây là 2 hàm aver và wtxt. Chúng có thể được dùng trong nhiều các chương trình khác sau này.

Để các hàm này thật sự trở thành public, có phạm vi áp dụng rộng rãi, có 2 điểm cần chú ý khi xây dựng chúng:

- Có tính độc lập cao, không phụ thuộc vào các tùy chọn của người dùng cũng như các điều kiện ràng buộc khác. Ví dụ, hàm aver trên không liên quan gì đến các điều kiện chọn và lọc đối tượng, list L mặc nhiên xem như chỉ chứa toàn các số thực.

- Các hàm có đối số (argument) thường có phạm vi áp dụng rộng hơn hàm không có đối số.

Nếu chúng ta có ý thức về điều này và luôn luôn có định hướng trong khi lập trình thì số lượng public functions tích lũy được sẽ ngày càng nhiều. Đây chính là nguồn tài nguyên, là “vốn liếng” quý giá của lập trình viên. Khi lập một chương trình mới, nếu cần chỉ copy y chang hoặc chỉnh sửa chút ít. Giá trị sử dụng của chúng càng cao hơn khi ta thiết lập được một chương trình lớn, một hệ thống lisp tương đối hoàn chỉnh. Toàn bộ các public functions trên được chuẩn hóa và cho autoload, khi cần thì gọi, đơn giản và tự nhiên như khi bạn gọi các hàm có sẵn của chính AutoLisp (setq, car, cadr, atof, itoa, if, while…)

 

3) Ghi text ra màn hình graphic

Như đã nói trên, có một rắc rối nhỏ, nhưng nếu không chú ý, nó sẽ thành “lớn chuyện”. Một chương trình rất “hoành tráng” có thể bị exit ngang xương vì một lỗi không đáng có.

Bạn hãy thử vào Text Style, đặt giá trị Height = 0.0000, sau đó thực hiện lệnh text:

Command: text

Specify start point of text or [Justify/Style]: Pickpoint

Specify height <2.5000>: Enter

Specify rotation angle of text <0>: Enter

Enter text: abcd - Enter

Enter text: Enter

Biểu thức lisp tương ứng là:

(command “text” (getpoint) 2.5 0 “abcd”)

 

Tiếp theo, vào lại Text Style, đặt Height khác 0, thử lại lệnh text:

Command: text

Specify start point of text or [Justify/Style]: Pickpoint

Specify rotation angle of text <0>: Enter

Enter text: abcd - Enter

Enter text: Enter

 

Điểm khác nhau giữa 2 lần là gì? Lần sau không yêu cầu Specify height nữa. Biểu thức lisp nếu viết như trên bị thừa 1 đối số, AutoCAD sẽ tạo ra text có góc xoay 2,5 độ và nội dung là “0”! Đối số cuối cùng “abcd” được hiểu là “hãy thực hiện lệnh abcd”! Kết quả:

Command: abcd Unknown command "ABCD". Press F1 for help.

Và tiếp theo tất nhiên là exit. Toàn bộ các câu lệnh sau đó sẽ không được thực hiện.

Thật đáng tiếc, khá nhiều người trong chúng ta đã mắc lỗi này (ngay cả chính bản thân ssg trước đây!).

Trên thực tế, người dùng có thể chọn 1 trong 2 dạng trên, tùy theo thói quen sử dụng. Chương trình phải tùy biến theo từng trường hợp cụ thể. Lỗi là do không để ý, đã biết rồi thì có rất nhiều cách tránh. Và nó đã “lôi thôi” như vậy thì tốt hơn hết là xây dựng luôn thao tác ghi text ra màn hình graphic thành 1 public function. Hàm wtxt trên là một hướng khả dĩ. Ta chỉ cần gọi nó với 2 đối số: txt (nội dung text) và p (điểm chèn), nó sẽ thực hiện các thao tác y như người dùng gọi lệnh text (với Text Style, Text Height và Width Factor theo các giá trị hiện hành)

 

4) Dữ liệu kiểu list

Lisp là gì? Câu này đã có nhiều người hỏi và nhiều người trả lời. Với lập trình viên, chỉ đơn giản là LISt Processing – Xử lý danh sách. Do đó, kiểu dữ liệu list (List Data Type) mặc nhiên là kiểu dữ liệu quan trọng nhất của ngôn ngữ lisp. AutoLisp cung cấp rất nhiều functions để thao tác với list. Mọi nguồn thông tin và dữ liệu phức tạp, bạn hãy cố gắng convert chúng về dữ liệu kiểu list, vấn đề sẽ đơn giản đi rất nhiều.

Trong ví dụ trên, ta hoàn toàn có thể xây dựng hàm aver với đối số là Selection Set (SS). Nhưng khi đó, bạn sẽ không dùng được hàm foreach rất gọn gàng như trên mà phải dùng repeat hoặc while, và chắc chắn code sẽ dài hơn. Đó là nói cụ thể trong bài này, còn trong nhiều trường hợp khác, rất nhiều kiểu dữ liệu sẽ dễ dàng chuyển sang list nhưng không thể chuyển được thành SS. Điều đó có nghĩa là hàm aver được xây dựng với đối số list sẽ có phạm vi áp dụng rộng rãi hơn là dùng đối số SS.

Tóm lại, muốn làm chủ Lisp, bạn phải biết cách làm chủ List Data Type!

 

5) Command và Entmake

Trong hàm wtxt trên, ta có thể dùng 2 cách ghi text: dùng entmake hoặc gọi command, kết quả cuối cùng là như nhau. Nhưng tại sao lại là entmake mà không là command? Lý do trong trường hợp này thì đã rõ, gọi command cho text vướng phải “vấn đề rắc rối” đã nêu (tất nhiên là vẫn xử lý được nhưng dài dòng hơn). Còn nói chung, mỗi cách đều có ưu và nhược điểm.

Cụ thể, hàm Command có các ưu điểm sau:

- Đơn giản, dễ hiểu, dễ nhớ. Gọi lệnh AutoCAD thế nào thì dùng command như vậy.

- Sau khi thực hiện command, AutoCAD trả về điểm đồ họa cuối cùng, được lưu trong biến “lastpoint”. Đây là điểm tham chiếu quan trọng cho chương trình khi cần thực hiện nhiều thao tác liên tiếp trên màn hình graphic.

Ví dụ, đoạn code sau vẽ liên tiếp 10 bậc thang từ điểm chuẩn p. Entmake không làm thay đổi “lastpoint” nên không dùng được như vậy:

(setq p (getpoint))

(repeat 10

(command "line" p (list (car p) (+ (cadr p) 20))

(list (+ (car p) 25) (+ (cadr p) 20)) "")

(setq p (getvar "lastpoint")))

)

Nhược điểm:

- Hàm command bị ảnh hưởng của biến osmode. Phải save os, disable os, rồi reset os (lôi thôi quá!)

- Tốc độ thực hiện hàm command chậm hơn entmake. Điều này đặc biệt quan trọng khi khối lượng thao tác khá nhiều.

Ưu điểm của command chính là nhược điểm của entmake và ngược lại. Tùy trường hợp cụ thể mà lựa chọn sử dụng.

 

6) Return value

Ngôn ngữ lisp có một ưu điểm tuyệt vời: mọi biểu thức lisp đều hoàn trả (return) một giá trị nào đó. Giá trị ấy có thể thuộc những kiểu dữ liệu khác nhau, nhưng về mặt logic đều được hiểu là T (true). Nếu không, nó hoàn trả nil (được hiểu là false). Điều này có lẽ hơi khó hiểu với người mới tiếp cận lisp. Nhưng khi đã quen, bạn sẽ thấy rất “khoái” vì nó giúp cho code của bạn ngắn gọn hơn rất nhiều.

Ví dụ 1, kết thúc hàm aver, thay vì theo logic thông thường, bạn phải viết là:

(if (/= L nil)

(setq divi (/ tot (length L)))

(setq divi nil)

)

(setq result divi)

Nhưng chỉ cần 1 dòng đơn giản:

(if L (/ tot (length L)))

Giá trị hoàn trả của phép chia (/ tot (length L)) cũng là giá trị hoàn trả của hàm if, và cũng là giá trị hoàn trả của cả function aver.

 

Ví dụ 2, đoạn Filter Objects ở trên, thay vì phải viết:

(setq OK T)

(while (= OK T)

(setq e (ssname ss 0))

(if (/= e nil)

(progn

(setq v (cdr (assoc 1 (entget e))))

(if (= (atof v) (read v)) (setq L (append L (list (atof v)))))

(ssdel e ss)

)

(setq OK nil)

)

)

 

Bạn chỉ cần đơn giản:

(while (setq e (ssname ss 0))

(setq v (cdr (assoc 1 (entget e))))

(if (= (atof v) (read v)) (setq L (append L (list (atof v)))))

(ssdel e ss)

)

Biểu thức (setq e (ssname ss 0)) có vai trò “đa nhiệm”, vừa thực hiện phép gán, vừa là biểu thức điều kiện cho hàm while, vừa tạo “rào cản” ngay từ đầu, không cần phải rào thêm (if (/= e nil)… bên trong nữa. Một công đôi ba việc, đúng là tuyệt vời!

 

Viết lan man rồi cũng phải đến hồi kết, nếu không sẽ bị cho là… lạc đề.

Lập trình là nghệ thuật – lập trình viên là nghệ sĩ! Đã là nghệ sĩ, ai cũng có cái “máu” ngang tàng, và chương trình mỗi người lập ra sẽ mang đậm dấu ấn cá nhân. Thế nhưng, mỗi người chúng ta đã đến với CadViet và giao lưu với nhau, dù muốn hay không cũng có sự ảnh hưởng qua lại lẫn nhau. Bản thân ssg, từ khi tham gia CadViet cũng đã học hỏi được nhiều điều. Đến giờ, trong các chương trình mình lập ra, không thể phân biệt được cái nào là kiến thức “kinh điển”, cái nào tự sáng tác, cái nào học được của anh Hoành, cái nào chịu ảnh hưởng của vndes… Tất cả đã kết hợp lại theo một phong cách mới.

Một ngày đẹp trời nào đó, bạn lang thang trên mạng và sưu tầm được một đoạn lisp. Nhìn vào cung cách coding, có thể bạn sẽ nhận ra ngay đây là phong cách của anh em nhà mình – phong cách lập trình CadViet!

 

Kết thúc bài này, ssg có một đề bài… thách đấu:

Lập lại public function (defun wtxt(txt p)…) ở trên thành dạng tối giản. Muốn viết kiểu gì cũng được nhưng phải bảo đảm có chức năng y chang như trên. Tiêu chí đánh giá duy nhất là kích thước bytes càng nhỏ càng tốt. Thống nhất giữ nguyên function name là wtxt (đừng giản lược đến mức chỉ còn có 1 chữ w!).

Nào, xin mời các nghệ sĩ lập trình CadViet!

  • Vote tăng 4

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác

Bài viết của Ssg rất hay.

 

Đây là một thử nghiệm của hàm wtxt rút gọn.

(defun wtxt (txt p)

(entmake (list (cons 0 "TEXT") (cons 1 txt) (cons 10 p) (cons 40 (getvar "textsize"))))

)

 

tuy nó ngắn hơn nhưng thiếu mất chức năng widthfactor như hàm của Ssg.

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác

Cảm ơn Bác ssg đã nhắc đến tên mình trong bài viết

Phải công nhận là trên này có rất nhiều cao thủ mà thấy mình cần học hỏi

Về nghệ thuật lập trình thì không bàn tới, nhưng về cách thức lập trình thì xin đưa ra vài ý kiến sau

 

1. Với mỗi method chỉ nên làm một chức năng

2. Với mỗi dòng code thì không nên để quá nhiều hàm

 

Có thể những ứng dụng tương đối nhỏ thì mọi người có thể không cần tách ra làm gì, nhưng điều đó là rất cần thiết khi phải làm những dự án lớn.

Việc code như thế nào là thói quen của mỗi người nhưng tất cả phải hướng đến sự trong sáng và rõ ràng

 

KISS = KEEP IT SAMPLE & STUPID

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
Bài viết của Ssg rất hay.

 

Đây là một thử nghiệm của hàm wtxt rút gọn.

(defun wtxt (txt p)

(entmake (list (cons 0 "TEXT") (cons 1 txt) (cons 10 p) (cons 40 (getvar "textsize"))))

)

 

tuy nó ngắn hơn nhưng thiếu mất chức năng widthfactor như hàm của Ssg.

Không được đâu anh Hoành! Vì không đúng yêu cầu của đề bài: wtxt phải hoạt động y như lệnh text, nghĩa là mặc định lấy tất các tham số đang hiện hành của textstyle. Yêu cầu này là chính đáng vì nó không gây phiền hà cho người dùng. Họ sẽ không có sự phân biệt nào giữa text tạo bằng lệnh text và bằng wtxt.

Chưa xét đến widthfactor, nếu người dùng chọn textheigh khác 0 thì lệnh text bình thường sẽ lấy giá trị này để viết text. Trong khi hàm wtxt luôn luôn lấy textsize để thực hiện (hai giá trị này có thể bằng hoặc khác nhau)

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
Không được đâu anh Hoành! Vì không đúng yêu cầu của đề bài: wtxt phải hoạt động y như lệnh text, nghĩa là mặc định lấy tất các tham số đang hiện hành của textstyle. Yêu cầu này là chính đáng vì nó không gây phiền hà cho người dùng. Họ sẽ không có sự phân biệt nào giữa text tạo bằng lệnh text và bằng wtxt.

Chưa xét đến widthfactor, nếu người dùng chọn textheigh khác 0 thì lệnh text bình thường sẽ lấy giá trị này để viết text. Trong khi hàm wtxt luôn luôn lấy textsize để thực hiện (hai giá trị này có thể bằng hoặc khác nhau)

thế thì đúng là chẳng còn cách nào ngoài cấu trúc của Ssg.

 

Nhưng có thể cải tiến 1 chút đoạn mã của Ssg (màu đỏ là bỏ đi, màu xanh là thêm vào)

(defun wtxt(txt p / sty d h1 h2 wf h)

(setq

sty (getvar "textstyle")

d (tblsearch "style" sty)

h1 (cdr (assoc 40 d))

h2 (cdr (assoc 42 d))

wf (cdr (assoc 41 d))

)

(if (> h1 0) (setq h h1) (setq h h2))

(entmake (list (cons 0 "TEXT") (cons 7 sty) (cons 40 h) (if (> h1 0)(cons 40 h1)(assoc 40 d)) (cons 41 wf)(assoc 41 d)(cons 1 txt) (cons 10 p)))

)

 

Và nó trở thành

(defun wtxt (txt p / sty d h1)

(setq

sty (getvar "textstyle")

d (tblsearch "style" sty)

h1 (cdr (assoc 40 d))

)

(entmake (list (cons 0 "TEXT")(if (> h1 0)(cons 40 h1)(assoc 40 d)) (assoc 41 d) (cons 1 txt) (cons 10 p)))

)

 

Nôi dung cải tiến:

- bỏ (cons 7 sty) vì mặc định entmake sẽ cho style hiện hành vào.

- bỏ biến h2 và h, thay vào đó là lồng câu lệnh rẽ nhánh vào entmake luôn.

- bỏ biến wf và lệnh gán vì 2 lệnh (setq wf (cdr (assoc 41 d))) và (cons 41 wf) sẽ được thay bằng (assoc 41 d)

 

Cá nhân mình rất thích và quan tâm đến các mẹo cải tiến. Nếu ai biết về javascript, hãy đọc mã của trang www.google.com, giống hệt tư tưởng của chủ đề này. Họ tối giản dòng lệnh đến tối đa, tiết kiệm từng byte, cũng vì vậy mà vào google.com nhanh như điện.

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác

Không bỏ được (cons 7 sty)! Nếu bỏ, nó lấy style STANDARD. Nếu trên máy anh chạy đúng thì có lẽ do option như thế nào đó. Nhưng chỉ cần 1 trường hợp không đúng là không được. Chương trình phải luôn luôn đúng trong mọi trường hợp!

Mình tạm hài lòng với code sau:

 

(defun wtxt (txt p / sty d h)
(setq
sty (getvar "textstyle")
d (tblsearch "style" sty)
h (cdr (assoc 40 d))
)
(entmake (list (cons 0 "TEXT") (cons 7 sty) (cons 1 txt) (cons 10 p)
	  (if (> h 0) (cons 40 h) (assoc 40 d)) (assoc 41 d))
)
)

 

Bạn nào có ý hay hơn không?

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
Không bỏ được (cons 7 sty)! Nếu bỏ, nó lấy style STANDARD. Nếu trên máy anh chạy đúng thì có lẽ do option như thế nào đó. Nhưng chỉ cần 1 trường hợp không đúng là không được. Chương trình phải luôn luôn đúng trong mọi trường hợp!

Mình tạm hài lòng với code sau:

 

(defun wtxt (txt p / sty d h)
(setq
sty (getvar "textstyle")
d (tblsearch "style" sty)
h (cdr (assoc 40 d))
)
(entmake (list (cons 0 "TEXT") (cons 7 sty) (cons 1 txt) (cons 10 p)
	  (if (> h 0) (cons 40 h) (assoc 40 d)) (assoc 41 d))
)
)

 

Bạn nào có ý hay hơn không?

:) :) :)

Hàm entmake sẽ lấy các thông số current cho các dxf của đối tượng mới mà.

Ví dụ khi thiếu dxf 8 thì layer hiện hành sẽ được cho vào, thiếu dxf 7 thì text style hiện hành sẽ được cho vào,...

Để tôi thử lại với các máy khác, vì cái này tôi đã làm đủ với các phiên bản ACAD trên máy mình mà vẫn OK.

bác Ssg cũng thử check lại xem!

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
:) :) :)

Hàm entmake sẽ lấy các thông số current cho các dxf của đối tượng mới mà.

Ví dụ khi thiếu dxf 8 thì layer hiện hành sẽ được cho vào, thiếu dxf 7 thì text style hiện hành sẽ được cho vào,...

Để tôi thử lại với các máy khác, vì cái này tôi đã làm đủ với các phiên bản ACAD trên máy mình mà vẫn OK.

bác Ssg cũng thử check lại xem!

Đã check lại rồi, vẫn như vậy.

Mình up file *.dwg đang thử lên nhưng không được, chẳng biết lý do gì!

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác

Đây là file testwtxt.dwg mà ssg chạy thử. Textstyle hiện hành là style1, font vnsimplex.shx.

Dòng "abcd" trên là không có (cons 7 sty), nó cứ lấy STANDARD để ghi text.

Dòng dưới có dùng (cons 7 sty). Kết quả đúng như ý.

Anh Hoành xem lại thử:

 

http://www.esnips.com/doc/0d8e7ccd-9b91-4d...dff899/TestWtxt

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác

Chào các bạn yêu Lisp. Tôi là người chưa biết Lisp, và cũng mới đến với VBA. Thấy Lisp khó hiểu quá, VBA có vẻ dễ hơn. Xin các bạn cho những ví dụ ứng dụng của Lisp "trong thực tế" mà các bạn đã dùng cho công việc của bạn, để tôi bắt chước áp dụng với công việc của mình. Xin cảm ơn.

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
Chào các bạn yêu Lisp. Tôi là người chưa biết Lisp, và cũng mới đến với VBA. Thấy Lisp khó hiểu quá, VBA có vẻ dễ hơn. Xin các bạn cho những ví dụ ứng dụng của Lisp "trong thực tế" mà các bạn đã dùng cho công việc của bạn, để tôi bắt chước áp dụng với công việc của mình. Xin cảm ơn.

Bạn chịu khó đọc thêm một số bài trong diễn đàn về Lisp sẽ thấy hiệu quả của nó. Tất cả những trình lisp đã post, hay dở thế nào chưa xét, nhưng đều mang tính thực tế. Không ai rảnh rỗi đến mức bỏ công lập trình để chơi cho vui!

VBA đúng là có vẻ dễ hơn, nhưng VBA có 1 nhược điểm rất lớn: khi AutoCAD lên đời, trình VBA của bạn có thể không chạy được nếu không nâng cấp bổ sung. Lisp thì vô tư đi, những trình Lisp được lập từ thời R12 đến nay vẫn chạy tốt với Acad2008!

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác

Mọi người cho mình hỏi , làm sao để lisp lưu lại 1 mặc định để dùng cho lần sau .

Cụ thể : Mình vẽ ký hiệu hàn , lần đầu tiên sẽ hỏi người dùng chiều cao hàn , mình muốn lần dùng tiếp theo cứ đánh enter tiếp tục thì nó sẽ dùng các giá trị đã nhập trước đó .

Mình đã thử không khai báo nó là biến local rồi mà nó vẫn bị "quên" sau khi lisp kết thúc .

 

(setq TT1 (getstring "\nWeld height <NONE>:"))

(if TT1 nil (setq TT1 TT1M))

(if (= TT1 "N") (setq TT1 nil))

(setq TT1M TT1)

 

Mình bỏ lửng không khai báo TT1M . Lần dùng tiếp theo em muốn người dùng có 3 lựa chọn : 1 là enter để dùng lại giá trị trước đó , 2 là nhập N để set giá trị NONE , 3 là nhập 1 giá trị mới .

 

Cám ơn mọi người .

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
Mọi người cho mình hỏi , làm sao để lisp lưu lại 1 mặc định để dùng cho lần sau .

Cụ thể : Mình vẽ ký hiệu hàn , lần đầu tiên sẽ hỏi người dùng chiều cao hàn , mình muốn lần dùng tiếp theo cứ đánh enter tiếp tục thì nó sẽ dùng các giá trị đã nhập trước đó .

Mình đã thử không khai báo nó là biến local rồi mà nó vẫn bị "quên" sau khi lisp kết thúc .

 

(setq TT1 (getstring "\nWeld height :"))

(if TT1 nil (setq TT1 TT1M))

(setq TT1M TT1)

 

Mình bỏ lửng không khai báo TT1M .

 

Cám ơn mọi người .

Đây là kỹ thuật mang tính "kinh điển". Có 3 thao tác cơ bản:

 

;;;1) Đầu chương trình, khởi tạo global variable TT0 (nếu chưa có TT0 thì tạo nó và gán giá trị ban đầu, ví dụ bằng 5):

(if (not TT0) (setq TT0 5))

 

;;;2) User nhập TT1. Giá trị mặc định TT0 nằm trong cặp dấu móc nhọn. Nếu user chấp nhận giá trị này thì chỉ bấm Enter thay vì nhập số + Enter. Đây cũng chính là phong cách của Acad, có tác dụng tiết kiệm thao tác cho user trong 1 số lệnh, như offset chẳng hạn:

(setq TT1 (getreal (strcat "\nWeld height <" (rtos TT0) ">: ")))

 

;;;3) Nếu user không nhập số, chỉ bấm Enter -> Gán TT0 cho TT1. Nếu user có nhập số + Enter thì gán TT1 cho TT0 (cập nhật TT0):

(if (not TT1) (setq TT1 TT0) (setq TT0 TT1))

  • Vote tăng 1

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
Mọi người cho mình hỏi , làm sao để lisp lưu lại 1 mặc định để dùng cho lần sau .

Cụ thể : Mình vẽ ký hiệu hàn , lần đầu tiên sẽ hỏi người dùng chiều cao hàn , mình muốn lần dùng tiếp theo cứ đánh enter tiếp tục thì nó sẽ dùng các giá trị đã nhập trước đó .

Mình đã thử không khai báo nó là biến local rồi mà nó vẫn bị "quên" sau khi lisp kết thúc .

 

(setq TT1 (getstring "\nWeld height <NONE>:"))

(if TT1 nil (setq TT1 TT1M))

(if (= TT1 "N") (setq TT1 nil))

(setq TT1M TT1)

 

Mình bỏ lửng không khai báo TT1M . Lần dùng tiếp theo em muốn người dùng có 3 lựa chọn : 1 là enter để dùng lại giá trị trước đó , 2 là nhập N để set giá trị NONE , 3 là nhập 1 giá trị mới .

 

Cám ơn mọi người .

 

Bạn có thể dùng một số biến hệ thống mà ACAD dành sẵn cho người dùng sử dụng như sau:

Để chứa số nguyên có 5 biến, gồm : USERI1,USERI2....USERI5; các biến này lưu cùng bản vẽ, giá trị mặc định ban đầu là 0.

Để chứa số thực có 5 biến, gồm : USERR1,USERR2....USERR5; các biến này lưu cùng bản vẽ., giá trị mặc định ban đầu là 0.0000.

Để chứa string có 5 biến, gồm : USERS1,USERS2....USERS5; các biến này không được lưu., giá trị mặc định ban đầu là "".

Chúc Bạn thành công!

  • Vote tăng 2

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác

Cám ơn hnhmai nhiều . Mình đã dùng biến useri1 để nhớ giá trị mặc định . Chạy tốt .

Bác ssg nói mà em chưa hiểu kỹ lắm , cuối chương trình có động tác gán giá trị mới cho TT0 nhưng đầu chương trình lại gán cho TT0 giá trị mặc định ví dụ là 5 , vậy lần sau chạy chương trình nữa thì giá trị mặc định luôn luôn bị set sẵn là 5 chứ ko phải là giá trị vừa dùng trước đó .

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
Cám ơn hnhmai nhiều . Mình đã dùng biến useri1 để nhớ giá trị mặc định . Chạy tốt .

Bác ssg nói mà em chưa hiểu kỹ lắm , cuối chương trình có động tác gán giá trị mới cho TT0 nhưng đầu chương trình lại gán cho TT0 giá trị mặc định ví dụ là 5 , vậy lần sau chạy chương trình nữa thì giá trị mặc định luôn luôn bị set sẵn là 5 chứ ko phải là giá trị vừa dùng trước đó .

Bạn đã thử chưa? TT0 không được khai báo local. Nó vẫn tồn tại suốt phiên làm việc cho đến khi bạn close bản vẽ. Do đó, khi bạn chạy lần 2 trở đi, nó sẽ không gán giá trị 5 nữa mà vẫn giữ giá trị (setq TT0 TT1) trước đó!

 

@hnhmai: Giờ mình mới biết đến các User variables này. Cám ơn bạn!

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
Mọi người cho mình hỏi , làm sao để lisp lưu lại 1 mặc định để dùng cho lần sau .

Cụ thể : Mình vẽ ký hiệu hàn , lần đầu tiên sẽ hỏi người dùng chiều cao hàn , mình muốn lần dùng tiếp theo cứ đánh enter tiếp tục thì nó sẽ dùng các giá trị đã nhập trước đó .

Mình đã thử không khai báo nó là biến local rồi mà nó vẫn bị "quên" sau khi lisp kết thúc .

 

(setq TT1 (getstring "\nWeld height <NONE>:"))

(if TT1 nil (setq TT1 TT1M))

(if (= TT1 "N") (setq TT1 nil))

(setq TT1M TT1)

 

Mình bỏ lửng không khai báo TT1M . Lần dùng tiếp theo em muốn người dùng có 3 lựa chọn : 1 là enter để dùng lại giá trị trước đó , 2 là nhập N để set giá trị NONE , 3 là nhập 1 giá trị mới .

 

Cám ơn mọi người .

 

;---------------------------------------

(defun nstr (stri def)

(princ stri)

(princ "<")

(princ " ")

(princ def)

(princ ">")

(princ ":")

(princ " ")

);defun nstr

;--------------------

(defun nstr1 (stri)

(princ stri)

(princ "<")

(princ "Nhap vao")

(princ ">")

(princ ":")

(princ " ")

);defun nstr1

;---------------------

(defun nint (prompt def / temp)

(if def

(setq temp (getint (nstr prompt def)))

(setq def (getint (nstr1 prompt)))

);if def

(if temp

(setq def temp)

def

);if temp

);defun nint

;---------------------

Đây là đoạn lisp mình sưu tầm được (từ đĩa CD bán trên thị trường)

Đoạn trên định nghĩa hàm nint sau này bạn chỉ việc làm như sau:

 

(setq slcuaso (nint "\nSo luong canh cua so: 1/2/3/4/5/6 "slcso1))

(setq slcso1 slcuaso)

sẽ cho ra biến slcuaso nếu lần đầu thì nó bảo bạn nhập vào, sau đó lưu lại cho các lần sau.

Trong đó slcuaso và slcso1 bạn có thể thay bằng bất kỳ cái gì khác điều được. Cái đoạn ở trên bạn chỉ tải 1 lần sau đó đoạn bên dưới này áp dụng cho tất cả các ứng dụng.

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
Bạn đã thử chưa? TT0 không được khai báo local. Nó vẫn tồn tại suốt phiên làm việc cho đến khi bạn close bản vẽ. Do đó, khi bạn chạy lần 2 trở đi, nó sẽ không gán giá trị 5 nữa mà vẫn giữ giá trị (setq TT0 TT1) trước đó!

 

@hnhmai: Giờ mình mới biết đến các User variables này. Cám ơn bạn!

 

Mình đã thử nhưng tt0 sẽ vẫn lưu giá trị 5 ( theo ví dụ của ssg ) , nên mình thêm dòng if trước đó

 

(if (= tt0 nil) (setq tt0 0))

 

Lần chạy đầu tiên tt0 chưa có giá trị thì sẽ được set 0 , sau lần này tt0 đã được mang giá trị nên sẽ không được set nữa , không biết có phải ý ssg là vậy .

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
Mình đã thử nhưng tt0 sẽ vẫn lưu giá trị 5 ( theo ví dụ của ssg ) , nên mình thêm dòng if trước đó

 

(if (= tt0 nil) (setq tt0 0))

 

Lần chạy đầu tiên tt0 chưa có giá trị thì sẽ được set 0 , sau lần này tt0 đã được mang giá trị nên sẽ không được set nữa , không biết có phải ý ssg là vậy .

Nếu bạn thực hiện đúng hoàn toàn như mình gợi ý, dòng màu đỏ trên không thể xảy ra được. Bạn kiểm tra lại các dòng code sau đó có cái gì làm thay đổi TT0 hay không?

 

Bạn có thể thử chương trình đơn giản sau. User nhập chiều dài và chỉ định điểm chuẩn -> chương trình vẽ 1 line từ điểm chuẩn, nằm ngang, với chiều dài đã nhập. Chạy lần đầu, chiều dài mặc định là 100 đã gán. Các lần sau, giá trị mặc định là giá trị vừa chạy của lần kề trước đó.

(defun C:VD( / L p)
(if (not L0) (setq L0 100))
(setq L (getreal (strcat "\nChieu dai <" (rtos L0) ">: ")))
(if (not L) (setq L L0) (setq L0 L))
(setq p (getpoint "\nBase point:"))
(command "line" p (polar p 0 L) "")
(princ)
)

 

Tất nhiên, L0 chỉ nhớ trong phiên làm việc hiện tại. Bạn close bản vẽ nó sẽ mất.

Nếu bạn muốn nhớ luôn cho các phiên làm việc sau (save luôn trong bản vẽ) thì dùng các biến UserR1, UserR2... như bạn hnhmai đã bày ở trên.

 

Lưu ý thêm

1) Giả sử các biến L và L0 là string thì có khác một chút:

Thay vì: (if (not L) (setq L L0) (setq L0 L))

Sửa thành: (if (= L "") (setq L L0) (setq L0 L))

 

2) 3 biểu thức (not L), (= L nil) và (null L) tương đương nhau. Viết sao cũng được, tuỳ thói quen mỗi người.

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
Nếu bạn thực hiện đúng hoàn toàn như mình gợi ý, dòng màu đỏ trên không thể xảy ra được. Bạn kiểm tra lại các dòng code sau đó có cái gì làm thay đổi TT0 hay không?

 

Bạn có thể thử chương trình đơn giản sau. User nhập chiều dài và chỉ định điểm chuẩn -> chương trình vẽ 1 line từ điểm chuẩn, nằm ngang, với chiều dài đã nhập. Chạy lần đầu, chiều dài mặc định là 100 đã gán. Các lần sau, giá trị mặc định là giá trị vừa chạy của lần kề trước đó.

(defun C:VD( / L p)

(if (not L0) (setq L0 100))

(setq L (getreal (strcat "\nChieu dai <" (rtos L0) ">: ")))

(if (not L) (setq L L0) (setq L0 L))

(setq p (getpoint "\nBase point:"))

(command "line" p (polar p 0 L) "")

(princ)

)

 

Tất nhiên, L0 chỉ nhớ trong phiên làm việc hiện tại. Bạn close bản vẽ nó sẽ mất.

Nếu bạn muốn nhớ luôn cho các phiên làm việc sau (save luôn trong bản vẽ) thì dùng các biến UserR1, UserR2... như bạn hnhmai đã bày ở trên.

 

Lưu ý thêm

1) Giả sử các biến L và L0 là string thì có khác một chút:

Thay vì: (if (not L) (setq L L0) (setq L0 L))

Sửa thành: (if (= L "") (setq L L0) (setq L0 L))

 

2) 3 biểu thức (not L), (= L nil) và (null L) tương đương nhau. Viết sao cũng được, tuỳ thói quen mỗi người.

 

Hoàn toàn đồng ý với ssg , dòng if màu đỏ này là mấu chốt đây . Mình cũng đã nói đến dòng if này ở post trên . Cám ơn ssg đã rất nhiệt tình .

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác

đây là đoạn code ghi kết quả tính toán mà em hay dùng

(setq res (entsel "\n Chon text ghi ket qua, Click hoac Enter de ghi ket qua ra man hinh "))
(if res
(progn
(setq res (entget (car res)))
(setq res (subst (cons 1 (rtos L 2 2)) (assoc 1 res) res))
(entmod res)
)
(progn
(setq p (getpoint "\n Chon diem nhap ket qua" ))
(wtxt (rtos L 2 2) p)
)

 

Đối với kết quả là số nguyên thì nó không ghi 2 chữ số 0 sau dấu thập phân. Do nhu cầu trình bày bản vẽ được đẹp hơn, em muốn ghi kết quả luôn giữ 2 số 0 sau dấu phẩy.

ví dụ kết quả tính toán là 10 kết quả ghi là màn hình mà em muốn là 10.00 chứ không phải 10. như vậy em phải sửa đoạn code này như thế nào?

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác
đây là đoạn code ghi kết quả tính toán mà em hay dùng

(setq res (entsel "\n Chon text ghi ket qua, Click hoac Enter de ghi ket qua ra man hinh "))
(if res
(progn
(setq res (entget (car res)))
(setq res (subst (cons 1 (rtos L 2 2)) (assoc 1 res) res))
(entmod res)
)
(progn
(setq p (getpoint "\n Chon diem nhap ket qua" ))
(wtxt (rtos L 2 2) p)
)

 

Đối với kết quả là số nguyên thì nó không ghi 2 chữ số 0 sau dấu thập phân. Do nhu cầu trình bày bản vẽ được đẹp hơn, em muốn ghi kết quả luôn giữ 2 số 0 sau dấu phẩy.

ví dụ kết quả tính toán là 10 kết quả ghi là màn hình mà em muốn là 10.00 chứ không phải 10. như vậy em phải sửa đoạn code này như thế nào?

(setvar "dimzin" 0)

  • Vote tăng 2

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác

Em thấy trong Cad hay sử dụng một loại option điều kiện [Yes/No] để xem xét việc có thực hiện một bước nào đó trong quá trình thao tác lệnh hay không. hiện em đang muốn dùng loại option điều kiện này nhưng viết mãi không được đoạn code ấy.

hic! em nhức đầu với nó quá! mọi người giúp em với, em chịu thua với nó rồi. :bigsmile:

Chia sẻ bài đăng này


Liên kết tới bài đăng
Chia sẻ trên các trang web khác

Tạo một tài khoản hoặc đăng nhập để nhận xét

Bạn cần phải là một thành viên để lại một bình luận

Tạo tài khoản

Đăng ký một tài khoản mới trong cộng đồng của chúng tôi. Điều đó dễ mà.

Đăng ký tài khoản mới

Đăng nhập

Bạn có sẵn sàng để tạo một tài khoản ? Đăng nhập tại đây.

Đăng nhập ngay

×