Trang

Thứ Tư, 3 tháng 7, 2013

37.5 Lưới tam giác Delaunay


37.5.1   Giới thiệu

Lớp Delaunay_triangulation_2<Traits,Tds>  được thiết kế để đại diện cho lưới tam giác Delaunay tạo từ một tập hợp điểm trong mặt phẳng. Một lưới tam giác Delaunay thỏa mãn điều kiện vòng rỗng - empty circle property (còn gọi là điều kiện Delaunay property): bên trong đường tròn ngoại tiếp circumscribing của tam giác không chứa bất kỳ điểm nào của lưới của lưới tam giác đó. Với một tập hợp điểm được thiếp lập mà không có tập con của bốn điểm đồng tròn, lưới tam giác Delaunay là duy nhất, nó tương ứng với đường Vorôni về tập hợp các điểm. Lớp Delaunay_triangulation_2<Traits,Tds> dẫn xuất ra từ lớp Triangulation_2<Traits,Tds>.
Lớp Delaunay_triangulation_2<Traits,Tds>kế thừa các kiểu được định nghĩa bởi lớp cơ bản Triangulation_2<Traits,Tds>. Kiểu bổ sung, cung cấp bởi lớp traits, được định nghĩa để thể hiện sơ đồ Voronoi.

Lớp Delaunay_triangulation_2<Traits,Tds> ghi đè lên các hàm hành viên chịu trách nhiệm chèn, di chuyển hay loại bỏ một điểm từ lưới để duy trì điều kiện Delaunay. Nó cũng có hàm thành viên  (Vertex_handle nearest_vertex(const Point& p)) để trả lời yêu cầu tìm kiếm hàng xóm gần nhất và hàm thành viên để xây dựng các phần tử (đỉnh và cạnh) của sơ đồ Voronoi kép.

Trait hình học - Geometric traits

Trait hình học là một mô hình của khái niệm DelaunayTriangulationTraits_2 đã được nâng cao từ TriangulationTraits_2. Đặc biệt, khái niệm này cung cấp predicate side_of_oriented_circle cái mà cho trước 4 điểm p,q,r,s quyết định vị trí của các điểm với đường tròn đi qua 3 điểm p, q và r. Predicate side_of_oriented_circle đã xác định lưới tam giác Delaunay thật sự. Việc thay đổi predicate cho phép xây dựng các biến thể của lưới tam giácDelaunay cho các hệ đo lường khác nhau như L1 hoặc L hoặc bất kỳ metric nào được định nghĩa bởi đối tượng lồi nào. Tuy nhiên, người dùng một hệ đo khác phải cẩn thận rằng lưới tam giác được xây dựng phải là một lưới của đường bao lồi nghĩa là đường bao lòi phải là một cạnh Delaunay. Nó được cấp cho bất cứ hệ đo lồi mịn (như L2) và có thể được đảm bảo cho hệ đo khác (như L) bởi sự bổ sung đến tập hợp điểm của các điểm bao bên ngoài (sentinel point). Các lớp kernel CGAL và lớp Triangulation_euclidean_traits_2<R>là các mô hình của khái niệm DelaunayTriangulationTraits_2 cho hệ hình học Ơ-clit (euclidean). Lớp trait cho địa hình, Projection_traits_xy_3<R>,
Projection_traits_yz_3<R>, và
Projection_traits_xz_3<R>
đều là kiểu của DelaunayTriangulationTraits_2ngoại trừ việc chúng không thỏa mãn cả hai điều kiện hàm và truy vấn điểm gần nhất.

Thực thi

Chèn một điểm mới vào trong lưới Delaunay được thực hiện bằng cách sử dụng hàm thành viên chèn điểm của lưới tam giác cơ bản và tiếp đến tiến hành một chuỗi các phép đối xứng flip để khôi phục điều kiện Delaunay. Số lượng phép đối xứng phải thực hiện là O(d) nếu đỉnh mới có góc d trong lưới Delaunay cập nhật. Với các điểm phân bố một cách ngẫu nhiên, mỗi phép chèn mất một khoảng thời gian trung bình O(1), một khi điểm đã được định vị trong lưới. Việc gọi hàm loại bỏ trong lưới và trắc lượng lại lỗ hổng tạo ra sẽ làm cho tiêu chuẩn Delaunay được thỏa mãn. Việc loại bỏ đỉnh của góc d mất O(d2). Góc d là O(1) với một một điểm ngẫu nhiên trong lưới. Khi góc của điểm nhỏ hơn hoặc bằng ( 7) một thủ tục đặc biệt được sử dụng để cho phép gia tăng thời gian loại bỏ toàn cục bởi hệ số của 2 điểm ngẫu nhiên [Dev09].

Dịch chuyển của đỉnh v từ điểm p tới vị trị mới p',đầu tiên kiểm tra lưới được nhúng có còn đồng phẳng hay không sau khi dịch chuyển. Nếu có, việc di chuyển được tiến hành đơn giản bởi một chuỗi các phép đối xứng để xây dựng lại điều kiện Delaunay, cái mà là O(d) ở nơi d là góc đo của đỉnh sau khi dịch chuyển. Ngược lại, sự dịch chuyển được thực hiển bởi việc thêm một đỉnh tại vị trí mới và loại bỏ đỉnh cũ. Sự phức tạp là O(n) trong trường hợp xấu nhất, nhưng chỉ còn O(1 + δ√n) khi phân bố các đỉnh một cách đồng đều trong một đơn vị vuông, nơi δ là khoảng cách (Ơ-clit Euclidean) giữa vị trí mới và cũ.

Sau khi tiến hành định vị một điểm,  hàng xóm gần nhất của điểm được tìm thấy sau khoảng thời gian O(n) trong trường hợp xấu nhất, nhưng chỉ là O(1) nếu các đỉnh phân bố ngẫu nhiên.

37.5.2   Ví dụ: Địa hình Delaunay Terrain

Dưới đây là đoạn mã tạo một lưới tam giác Delaunay dựa trên hệ hình học Ơ-clit cho hình chiếu của mô hình địa hình. Các điểu có cao độ, là những điểm 3D, nhưng predicate được sử dụng để xây dựng lưới Delaunay được tính toán dựa trên tọa độ x và y của các điểm mà thôi.

Các lớp Projection_traits_xy_3<K> là một phần của Kernel hình học đồng phẳng 2D và 3D, và thay thế lớp Triangulation_euclidean_traits_xy_3<K>.

File: examples/Triangulation_2/terrain.cpp

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Projection_traits_xy_3.h>
#include <CGAL/Delaunay_triangulation_2.h>

#include <fstream>

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Projection_traits_xy_3<K> Gt;
typedef CGAL::Delaunay_triangulation_2<Gt> Delaunay;

typedef K::Point_3 Point;

int main()
{
std::ifstream in("data/terrain.cin");
std::istream_iterator<Point> begin(in);
std::istream_iterator<Point> end;

Delaunay dt(begin, end);
std::cout << dt.number_of_vertices() << std::endl;
return 0;
}


37.5.3   Ví dụ: Sơ đồ Voronoi


Đoạn mã dưới đây có tác dụng tính toán các cạnh của sơ đồ Voronoi dựa trên tập điểm và in số lượng của các cạnh hữu hạn và số lượng tia của sơ đồ.
 
File: examples/Triangulation_2/voronoi.cpp

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>

#include <fstream>

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;

typedef CGAL::Delaunay_triangulation_2<K> Triangulation;
typedef Triangulation::Edge_iterator Edge_iterator;
typedef Triangulation::Point Point;

int main( )
{
std::ifstream in("data/voronoi.cin");
std::istream_iterator<Point> begin(in);
std::istream_iterator<Point> end;
Triangulation T;
T.insert(begin, end);

int ns = 0;
int nr = 0;
Edge_iterator eit =T.edges_begin();
for ( ; eit !=T.edges_end(); ++eit) {
CGAL::Object o = T.dual(eit);
if (CGAL::object_cast<K::Segment_2>(&o)) {++ns;}
else if (CGAL::object_cast<K::Ray_2>(&o)) {++nr;}
}
std::cout << "The Voronoi diagram has " << ns << " finite edges "
<< " and " << nr << " rays" << std::endl;
return 0;
}

37.5.4   Ví dụ: In đường Voronoi giới hạn bởi hình chữ nhật

Đoạn mã dưới đây có tác dụng tính toán lưới tam giác Delaunay dựa trên tập hợp điểm và in đường Voronoi được giới hạn bởi hình chữ nhật cho trước.


Figure 37.6:  Sơ đồ Voronoi (màu đỏ) của các điểm màu đen được giới hạn bởi hình chữ nhật xanh.
 
File: examples/Triangulation_2/print_cropped_voronoi.cpp
 
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <iterator>

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_2 Point_2;
typedef K::Iso_rectangle_2 Iso_rectangle_2;
typedef K::Segment_2 Segment_2;
typedef K::Ray_2 Ray_2;
typedef K::Line_2 Line_2;
typedef CGAL::Delaunay_triangulation_2<K> Delaunay_triangulation_2;

//A class to recover Voronoi diagram from stream.
//Rays, lines and segments are cropped to a rectangle
//so that only segments are stored
struct Cropped_voronoi_from_delaunay{
std::list<Segment_2> m_cropped_vd;
Iso_rectangle_2 m_bbox;

Cropped_voronoi_from_delaunay(const Iso_rectangle_2& bbox):m_bbox(bbox){}

template <class RSL>
void crop_and_extract_segment(const RSL& rsl){
CGAL::Object obj = CGAL::intersection(rsl,m_bbox);
const Segment_2* s=CGAL::object_cast<Segment_2>(&obj);
if (s) m_cropped_vd.push_back(*s);
}

void operator<<(const Ray_2& ray) { crop_and_extract_segment(ray); }
void operator<<(const Line_2& line) { crop_and_extract_segment(line); }
void operator<<(const Segment_2& seg){ crop_and_extract_segment(seg); }
};

int main(){
//consider some points
std::vector<Point_2> points;
points.push_back(Point_2(0,0));
points.push_back(Point_2(1,1));
points.push_back(Point_2(0,1));

Delaunay_triangulation_2 dt2;
//insert points into the triangulation
dt2.insert(points.begin(),points.end());
//construct a rectangle
Iso_rectangle_2 bbox(-1,-1,2,2);
Cropped_voronoi_from_delaunay vor(bbox);
//extract the cropped Voronoi diagram
dt2.draw_dual(vor);
//print the cropped Voronoi diagram as segments
std::copy(vor.m_cropped_vd.begin(),vor.m_cropped_vd.end(),
std::ostream_iterator<Segment_2>(std::cout,"\n"));
}

Thứ Hai, 1 tháng 7, 2013

Chuyển đổi các dạng chuỗi ký tự trong ObjectARX và VS2005

Một vấn đề làm nhức nhối dân lập trình nghiệp dư như mình là chuyển đổi giữa các dạng ký tự (char, TCHAR, ACHAR, wtchar, string, CString, UNICODE, ANSI...). Nhiều lúc mình không phân biệt nổi, dẫn đến những lỗi rất ngớ ngẩn khi chạy ứng dụng ObjectARX. Những lỗi này có thể làm cho AutoCAD ngừng hoạt động theo kiểu "đột quỵ", ra đi không một lời từ biệt.
Nhưng con giun xéo lắm cũng phải oằn, hôm nay mình sẽ quyết tâm tìm hiểu đến nơi đến chốn vấn đề này.

Dưới đây là một số hàm chuyển đổi mình đã sưu tập được:

1. Sử dụng kiểu string phải thêm tệp tin tiêu đề string.h
# Include "string"
using namespace std;

2. Tương tự, khi sử dụng kiểu CString cũng phải thêm tệp tin tiêu đề (trong dự án không có MFC)
# Include "afx.h"
Chú ý Nếu gặp thông báo lỗi:
 # error: Building MFC application with / MD [d] (CRT dll version) requires MFC shared dll version. Please # define _AFXDLL Or do not use / MD [d] error.
Lúc này, bạn cần thay đổi cài đặt cho dự án:

Project | Properties | Configuration Properties | General | Use of MFC:
Use MFC in a Shared DLL (Project -> Properties -> Lựa chọn Use MFC in as share dll)

Sử dụng MFC cho dự án
3. wchar_t
wchar_t là một kiểu dữ liệu của C++, char is 8 character type, có thể chứa tới 256 loại Character, nhiều nhóm ký tự nước ngoài đặt số ký tự còn lớn hơn 256,  many foreign-language character sets the number of characters contained more than 256, character can not be represented

wchar_t data type is 16, the number of characters that can be represented far more than the char type.

4. Kiểu ACHAR (đây là kiểu dữ liệu được định nghĩa bởi AUTODESK trong tệp tin adachar.h:
typedef wchar_t ACHAR ;)
# include "adachar.h"

5. USES_CONVERSION được định nghĩa trong tệp tin atlconv.h
#include "atlconv.h"

6. Chuyển đổi từ string sang ACHAR *

string str = "string";
ACHAR * ach;
USES_CONVERSION;
ach = (ACHAR *) A2CT (str.c_str ());
7. Chuyển đổi từ ACHAR * sang string
ACHAR * ach;
USES_CONVERSION;
string temp = W2A (ach);

8. Chuyển đổi từ (Wchar_t) ACHAR * sang char *
char * ch;
ACHAR * ach;
USES_CONVERSION;
ch = T2A (ach);

9. Chuyển đổi từ char * sang ACHAR *
ACHAR * ach1;
char * ch;
Cách 1:
USES_CONVERSION;
ach1 = A2W (ch);
Cách 2:
size_t convertedChars = 0 ;
/ / record returns the actual length of the string conversion
mbstowcs_s (& convertedChars, ach1, 10, ch, _TRUNCATE) ;
/ / 10 is the maximum length of ch

Degrees, as needs change

10. Chuyển đổi từu int sang string
string str;
int nNumber = 10001;
char cT [10] ;/ / put int into a string
_itoa_s (nNumber, cT, 10);
str = cT; or string str (cT);

11. Chuyển đổi từ string sang int

12.Chuyển đổi từ CString sang char *
Cách 1:
char * ch;
CString temp;
ch = T2A (temp.GetBuffer (0));
Cách 2:
Using casts
CString theString ("This is a test");
LPTSTR lpsz = (LPTSTR) (LPCTSTR) theString;

13. Chuyển đổi từ char * sang CString
Cách 1:
Can directly assign
CString cstr;
char * ch;
cstr = ch;
Cách 2: Bằng cách sử dụng hàm format

char chArray [] = "This is a test";
CString cstr;
MBCS lower (ie not defined UNICODE time):
cstr.Format (_T ("% s"), chArray);
UNICODE is defined:
USES_CONVERSION;
cstr.Format (_T ("% s"), A2W (chArray));

14. Chuyển đổi từ char * sang int
char * ch;
int n = atoi (ch);

15. Chuyển đổi int sang char *
int n = 45;
char nCh [10];
char * ch;
itoa (n, nCh, 16) ;/ / 16 is hexadecimal, 2,8,10,16, also may take the following forms may be used: ch = itoa (n, nCh, 16);
VS2005 atmospheres: _itoa_s (n, nCh, 2);

16. Chuyển CString sang string
string str;
CString temp;
USES_CONVERSION;
str = T2A (temp.GetBuffer (0));

17. Chuyển string sang CString
CString cstr;
string str;
cstr = str.c_str ();

18. Chuyển char * sang string
Phương pháp xây dựng trực tiếp:
char cT1 [20];
string ste (cT1) ;/ / re-construct a string - Xây dựng lại chuỗi

19. Chuyển string sang char *
string ste;
ch = (char *) ste.c_str ();
By removing const char * conversion properties can only pay attention to ch

char *, not to char []

20. Chuyển từ float (hoặc double) sang string
char cT1 [20];
_gcvt_s  (cT1, 20,110.58485678,6) ;/ / 6  số sau dấu thập phân
string ste (cT1);

21. Chuyển string sang double
string num = "15.12054";
double d = atof (num.c_str ());

Chủ Nhật, 30 tháng 6, 2013

Lưu trữ đối tượng và tệp tin DWG và DXF (phần 2)

Thực thi hàm lưu trữ DWG

Khi thực thi hàm dwgOutFields()dwgInFields() cho một lớp mới, trước tiên bạn phải gọi phương thức assertReadEnabled() hoặc assertWriteEnabled() để đảm bảo rằng đối tượng đang được mở ở trạng thái phù hợp. 
Việc tiếp theo là lớp dẫn xuất phải gọi các hàm tương tự (ví dụ dwgOutFields()) trên lớp cơ sở liền kề. Quá trình này được gọi là siêu thông điệp (super messaging). Dưới đây là một ví dụ:
AcDbDerivedClass::dwgOutFields( ... );
{ 
   assertReadEnabled()
   myParent::dwgOutFields();
   // Perform class-specific operations after super-messaging.
}
Nếu quên gọi thông điệp phù hợp của lớp cơ sở hơn, bạn sẽ nhận được lỗi runtime.

Sau super-messaging (siêu thông điệp), bạn ghi hoặc đọc các trường field. Bạn có thể cải thiện quá trình bằng việc kiểm tra kiểu filer. Ví dụ, nếu kiểu filer là kIdXlateFiler và lớp không định nghĩa một kết nối tham chiếu, bạn có thể sửa lại đơn giản.
Với tệp tin DWG, bạn cần gọi thao tác ghi và đọc theo cùng trình tự. Nếu không, lớp dẫn xuất sẽ bị nhầm lẫn. Nếu có có liệu không xác định được kích thước, hãy kiểm tra kích thước trước tiên.

Ví dụ cho dwgOutFields()

Hầu hết filer đều gọi writeItem(), một hàm thành viên được quá tải (overload) cho tất cả các kiểu dữ liệu hỗ trợ. Ngoài ra còn có những hàm khác như writeInt32() được sử dụng trong ví dụ sau, mà có thể được sử dụng để hỗ trợ casting kiểu tự động. Các hàm như vậy bắt buộc tham số phải được xử lý như kiểu xác định bất chấp kiểu thực sự của nó trong bộ nhớ.
Chú ý Nếu lớp có dữ liệu integer, bạn cần sử dụng hàm đọc và ghi nào phù hợp nhất (ví dụ writeInt32)
Dưới đây là ví dụ từ AsdkPoly::dwgOutFields():
Acad::ErrorStatus
AsdkPoly::dwgOutFields(AcDbDwgFiler* filer) const
{
assertReadEnabled();
Acad::ErrorStatus es;
if ((es = AcDbCurve::dwgOutFields(filer))
!= Acad::eOk)
{
return es;
}

// Object Version - must always be the first item.
//
Adesk::Int16 version = VERSION;
filer->writeItem(version);

filer->writePoint2d(mCenter);
filer->writePoint2d(mStartPoint);
filer->writeInt32(mNumSides);
filer->writeVector3d(mPlaneNormal);
filer->writeString(mpName);
// mTextStyle is a hard pointer id, so filing it out to
// the purge filer (kPurgeFiler) prevents purging of
// this object.
//
filer->writeHardPointerId(mTextStyle);
filer->writeDouble(mElevation);
return filer->filerStatus();
}

Ví dụ cho dwgInFields()

Dưới đây là ví dụ cho AsdkPoly::dwgInFields()
Acad::ErrorStatus
AsdkPoly::dwgInFields(AcDbDwgFiler* filer)
{
assertWriteEnabled();
Acad::ErrorStatus es;
if ((es = AcDbCurve::dwgInFields(filer)) != Acad::eOk)
{
return es;
}

// Object Version - must always be the first item.
//
Adesk::Int16 version;
filer->readItem(&version);
if (version > VERSION)
return Acad::eMakeMeProxy;
switch (version)
{
case 1:
{
AcGePoint3d center;
filer->readPoint3d(&center);
AcGePoint3d startPoint;
filer->readPoint3d(&startPoint);
filer->readInt32(&mNumSides);
filer->readVector3d(&mPlaneNormal);
acutDelString(mpName);
filer->readString(&mpName);
filer->readHardPointerId(&mTextStyle);

//convert data from old format
acdbWcs2Ecs(asDblArray(center),asDblArray(center),
asDblArray(mPlaneNormal),Adesk::kFalse);
mCenter.set(center.x,center.y);
mElevation = center.z;

acdbWcs2Ecs(asDblArray(startPoint),asDblArray(startPoint),
asDblArray(mPlaneNormal),Adesk::kFalse);
mStartPoint.set(startPoint.x,startPoint.y);
assert(mElevation == startPoint.z);
break;
}
case 2:
filer->readPoint2d(&mCenter);
filer->readPoint2d(&mStartPoint);
filer->readInt32(&mNumSides);
filer->readVector3d(&mPlaneNormal);
acutDelString(mpName);
filer->readString(&mpName);
filer->readHardPointerId(&mTextStyle);
filer->readDouble(&mElevation);
break;
default:
assert(false);
}
return filer->filerStatus();
}

Thực thi hàm lưu trữ DXF

Nếu thực thi hàm dxfOutFields()dxfInFields() cho một lớp mới, lớp dẫn xuất của bạn phải gọi hàm assertReadEnabled() hoặc assertWriteEnabled() trước tiên. Sau đó là gọi các hàm tương trự trên lớp cơ sở liền kề (siêu thông điệp). 

DXF Group Code Ranges 

Thể hiện DXF của đối tượng là tổ hợp của các cặp mã nhóm và dữ liệu, với từng mã nhóm xác định kiểu dữ liệu. Khi định nghĩa thể hiện DXF của riêng mình, nhóm dữ liệu đầu tiên bạn ghi chép và read in phải là đánh dấu dữ liệu dưới lớp. Đánh dấu bao gồm mã nhóm 100 theo sau bởi sâu kí tự là tên lớp hiện hành. Sau đó, bạn chọn mã nhóm từ bảng sau tương ứng với các kiểu dữ liệu của mỗi trường dữ liệu bạn định sao chép.
Bảng mã nhóm DXF cho thể hiện đối tượng
Từ
Đến
Kiểu dữ liệu
1
4
Text
6
9
Text
10
17
Điểm hoặc vector (3 chiều)
38
59
Số thực Real
60
79
Số nguyên 16-bit integer
90
99
Số nguyên 32-bit integer
100
100
Đánh dấu dữ liệu Subclass
102
102
Text
140
149
Real
170
179
16-bit integer
210
219
3 reals
270
279
16-bit integer
280
289
8-bit integer
300
309
Text
310
319
Binary chunk
320
329
Handle
330
339
Soft pointer ID
340
349
Hard pointer ID
350
359
Soft owner ID
360
369
Hard owner ID

Một mã ID được dịch sang rlname. Ví dụ, AcDbObjectId tương ứng với một ads_name, được đại diện trong resval union là rlname.

Phụ thuộc thứ tự Order Dependence

Với DXF, trong miêu tả của tác giả lớp (lớp author's discretion), nhóm dữ liệu có thể thể hiện theo thứ tự tùy ý, hoặc bỏ qua tùy chọn. Một số lớp hỗ trợ hỗ trợ thứ tự độc lập của nhóm dữ liệu trong khi một số khác lại không. Nếu bạn cho phép độc lập thứ tự, khi đó hàm dxfInFields() phải sử dụng câu lệnh switch để lựa chọn hành động dựa trên giá trị mã nhóm. Độc lập thứ tự thường phù hợp với đối tượng có số lượng trường biết trước và không thay đổi. Đối tượng có chiều dài mảng thay đổi hoặc cấu trúc hướng tới order-dependent khi chúng được lưu và đọc.

Ví dụ cho dxfOutFields()  

Đoạn mã từ AsdkPoly::dxfOutFields()
Acad::ErrorStatus
AsdkPoly::dxfOutFields(AcDbDxfFiler* filer) const
{
assertReadEnabled();
Acad::ErrorStatus es;
if ((es = AcDbCurve::dxfOutFields(filer))
!= Acad::eOk)
{
return es;
}
filer->writeItem(AcDb::kDxfSubclass, "AsdkPoly");
// Object Version
//
Adesk::Int16 version = VERSION;
filer->writeInt16(AcDb::kDxfInt16, version);
filer->writePoint2d(AcDb::kDxfXCoord, mCenter);
filer->writePoint2d(AcDb::kDxfXCoord + 1, mStartPoint);
filer->writeInt32(AcDb::kDxfInt32, mNumSides);

// Always use max precision when writing out the normal.
filer->writeVector3d(AcDb::kDxfNormalX, mPlaneNormal,16);
filer->writeString(AcDb::kDxfText, mpName);
filer->writeItem(AcDb::kDxfHardPointerId, mTextStyle);
filer->writeDouble(AcDb::kDxfReal, mElevation);
return filer->filerStatus();
}

Ví dụ cho dxfInFields() với độc lập thứ tự Order Independence  

Ví dụ cho AsdkPoly::dxfInFields()
Acad::ErrorStatus
AsdkPoly::dxfInFields(AcDbDxfFiler* filer)
{
assertWriteEnabled();
Acad::ErrorStatus es = Acad::eOk;
resbuf rb;
if ((AcDbCurve::dxfInFields(filer) != Acad::eOk)
|| !filer->atSubclassData("AsdkPoly"))
{
return filer->filerStatus();
}
// Object Version
Adesk::Int16 version;
filer->readItem(&rb);
if (rb.restype != AcDb::kDxfInt16)
{
filer->pushBackItem();
filer->setError(Acad::eInvalidDxfCode,
"\nError: expected group code %d (version)",
AcDb::kDxfInt16);
return filer->filerStatus();
}

version = rb.resval.rint;
if (version > VERSION)
return Acad::eMakeMeProxy;

AcGePoint3d cen3d,sp3d;
AcGePoint2d cen2d,sp2d;
long numSides;
AcDbObjectId textStyle;
double elevation;
Adesk::UInt32 fieldsFlags = 0;
char * pName = NULL;
AcGeVector3d planeNormal;

while ((es == Acad::eOk)
&& ((es = filer->readResBuf(&rb)) == Acad::eOk))
{
switch (rb.restype) {
case AcDb::kDxfXCoord:
if (version == 1)
cen3d = asPnt3d(rb.resval.rpoint);
else
cen2d = asPnt2d(rb.resval.rpoint);
fieldsFlags |= 0x1;
break;
case AcDb::kDxfXCoord + 1:
if (version == 1)
sp3d = asPnt3d(rb.resval.rpoint);
else
sp2d = asPnt2d(rb.resval.rpoint);
fieldsFlags |= 0x2;
break;
case AcDb::kDxfInt32:
numSides = rb.resval.rlong;
fieldsFlags |= 0x4;
break;
case AcDb::kDxfNormalX:
planeNormal = asVec3d(rb.resval.rpoint);
fieldsFlags |= 0x8;
break;
case AcDb::kDxfText:
acutUpdString(rb.resval.rstring,pName);
fieldsFlags |= 0x11;
break;
case AcDb::kDxfHardPointerId:
acdbGetObjectId(textStyle, rb.resval.rlname);
fieldsFlags |= 0x12;
break;
case AcDb::kDxfReal:
if (version == 2)
{
fieldsFlags |= 0x10;
elevation = rb.resval.rreal;
break;
}
//fall through intentional
default:
// An unrecognized group. Push it back so that
// the subclass can read it again.
filer->pushBackItem();
es = Acad::eEndOfFile;
break;
}
}

// At this point, the es variable must contain eEndOfFile,
// either from readResBuf() or from pushbackBackItem(). If
// not, it indicates that an error happened and we should
// return immediately.
//
if (es != Acad::eEndOfFile)
return Acad::eInvalidResBuf;
// Now check to be sure all necessary group codes were
// present.
//
// Mandatory fields:
// - center
// - start point
// - normal
// - number of sides
// - elevation (if version > 1)
short required[] =
{AcDb::kDxfXCoord, AcDb::kDxfXCoord+1, AcDb::kDxfInt32,
AcDb::kDxfNormalX, AcDb::kDxfReal};

for (short i = 0; i < (version>1?4:3); i++) {
if (!fieldsFlags & 0x1) {
filer->setError(Acad::eMissingDxfField,
"\nMissing DXF group code: %d", 2, required[i]);
return Acad::eMissingDxfField;
} else
fieldsFlags >>= 1;
}
mPlaneNormal = planeNormal;
mNumSides = numSides;
mTextStyle = textStyle;
setName(pName);
acutDelString(pName);
if (version==1)
{
//convert data from old format
acdbWcs2Ecs(asDblArray(cen3d),asDblArray(cen3d),
asDblArray(planeNormal),Adesk::kFalse);
mCenter.set(cen3d.x,cen3d.y);
mElevation = cen3d.z;

acdbWcs2Ecs(asDblArray(sp3d),asDblArray(sp3d),
asDblArray(planeNormal),Adesk::kFalse);
mStartPoint.set(sp3d.x,sp3d.y);
assert(mElevation == sp3d.z);
} else {
mCenter = cen2d;
mStartPoint = sp2d;
mElevation = elevation;
}
return es;
}

Ví dụ cho dxfInFields() với phụ thuộc thứ tự

Đoạn code dưới đây trình bày cách bạn có thể ghi hàm dxfInFields() là một order-dependent. 
Acad::ErrorStatus
AsdkPoly::dxfInFields(AcDbDxfFiler* filer)
{
assertWriteEnabled();

if ((AcDbCurve::dxfInFields(filer) != Acad::eOk) ||
!filer->atSubclassData("AsdkPoly") )
{
return filer->filerStatus();
}

try
{
struct resbuf rb;

// Object Version
Adesk::Int16 version;
filer->readItem(&rb);
if (rb.restype != AcDb::kDxfInt16)
throw AcDb::kDxfInt16;

version = rb.resval.rint;
if (version > VERSION)
return Acad::eMakeMeProxy;

if (version == 1)
{
AcGePoint3d cent,sp;

filer->readItem(&rb);
if (rb.restype != AcDb::kDxfXCoord)
throw AcDb::kDxfXCoord
cent = asPnt3d(rb.resval.rpoint);

filer->readItem(&rb);
if (rb.restype != AcDb::kDxfXCoord + 1)
throw AcDb::kDxfXCoord + 1;
sp = asPnt3d(rb.resval.rpoint);

filer->readItem(&rb);
if (rb.restype != AcDb::kDxfInt32)
throw AcDb::kDxfInt32;
mNumSides = rb.resval.rlong;

filer->readItem(&rb);
if (rb.restype != AcDb::kDxfNormalX)
throw AcDb::kDxfNormalX
mPlaneNormal = asVec3d(rb.resval.rpoint);

filer->readItem(&rb);
if (rb.restype != AcDb::kDxfText)
throw AcDb::kDxfText;
setName(rb.resval.rstring);

filer->readItem(&rb);
if (rb.restype != kDxfHardPointerId)
throw AcDb::kDxfHardPointerId;
acdbGetObjectId(mTextStyle, rb.resval.rlname);

// Convert data from old format.
acdbWcs2Ecs(asDblArray(cent),asDblArray(cent),
asDblArray(mPlaneNormal),Adesk::kFalse);
mCenter.set(cent.x,cent.y);
mElevation = cent.z;

acdbWcs2Ecs(asDblArray(sp),asDblArray(sp),
asDblArray(mPlaneNormal),Adesk::kFalse);
mStartPoint.set(sp.x,sp.y);
assert(mElevation == sp.z);
}
else if (version == 2)
{
filer->readItem(&rb);
if (rb.restype != AcDb::kDxfXCoord)
throw AcDb::kDxfXCoord;
mCenter = asPnt2d(rb.resval.rpoint);

filer->readItem(&rb);
if (rb.restype != AcDb::kDxfXCoord + 1)
throw AcDb::kDxfXCoord + 1;
mStartPoint = asPnt2d(rb.resval.rpoint);

filer->readItem(&rb);
if (rb.restype != AcDb::kDxfInt32)
throw AcDb::kDxfInt32
mNumSides = rb.resval.rlong;

filer->readItem(&rb);
if (rb.restype != AcDb::kDxfNormalX)
throw AcDb::kDxfNormalX;
mPlaneNormal = asVec3d(rb.resval.rpoint);

filer->readItem(&rb);
if (rb.restype != AcDb::kDxfText)
throw AcDb::kDxfText
setName(rb.resval.rstring);

filer->readItem(&rb);
if (rb.restype != AcDb::kDxfHardPointerId)
throw AcDb::kDxfHardPointerId;
acdbGetObjectId(mTextStyle, rb.resval.rlname);

filer->readItem(&rb);
if (rb.restype != AcDb::kDxfReal)
throw AcDb::kDxfReal;
mElevation = rb.resval.rreal;
}
else assert(false);
}
catch (AcDb::DxfCode code)
{
filer->pushBackItem();
filer->setError(Acad::eInvalidDxfCode,
"\nError: expected group code %d", code);
return filer->filerStatus();
}
}

Tham chiếu đối tượng

Một tham chiếu đối tượng có thể là cứng hoặc mềm và nó có thể là tham chiếu sở hữu hay là một con trỏ tham chiếu. Sự phân biệt cứng và mềm chỉ ra liệu đối tượng được tham chiếu có cần thiết với sự tồn tại của đối tượng tham chiếu đến nó hay không. Một tham chiếu cứng chỉ ra một đối tượng phụ thuộc vào đối tượng tham chiếu để tồn tại. Một tham chiếu mềm có nghĩa là đối tượng có một số kiểu quan hệ với đối tượng đượng tham chiếu, nhưng không nhất thiết. Tham chiếu sở hữu dictate có bao nhiêu đối tượng được lưu. Nếu một đối tượng sở hữu một đối tượng khác, sau đó bất cứ khi nào đối tượng đầu tiên được file out, nó kéo theo đối tượng được sở hữu với nó. Bởi một đối tượng chỉ có duy nhất một chủ sở hữu, tham chiếu sở hữu được dùng cho phép ghi không thừa (nonredundant) của CSDL. Ngược lại, tham chiếu con trỏ được dùng để biệu thị một tham chiếu tuy fý giữa các đối tượng CSDL (AcDbObject). Tham chiếu con trỏ được dùng để hoàn thành (dư thừa) ghi ra CSDL.
Ví dụ, trong hình vẽ, đoạn thẳng kép chỉ ra tham chiếu sở hữu. Nếu theo sau đoạn thẳng kép này, bạn tiếp xúc vào mọi đối tượng trong CSDL nhỏ này chỉ một lần. Nếu bạn theo sau đoạn thẳng đơn, thể hiện tham chiếu con trỏ, bạn tiếp xúc vào đối tượng hơn một lần, bởi vì đa đối tượng có thể trỏ về cùng một đối tượng. Để có được "định nghĩa" đầy đủ về đối tượng AcDbLine, bạn cần thực hiện các tham chiếu cứng, tham chiếu cả sở hữu và con trỏ (nghĩa là: cả đoạn thẳng đơn và kép solid liền).

Lưu trữ đối tượng vào tệp tin DWG và DXF

Khi dẫn xuất một lớp từ AcDbObject, bạn cần phải bổ sung thông tin vào trong cơ chế lưu trữ thông tin của AutoCAD được nói đến ở trong chương này. Dưới đây là 4 hàm được sử dụng để cất giữ đối tượng vào tệp tin DWG và DXF. Nó còn được sử dụng với mục đích khác như nhân bản (cloning).

Acad::ErrorStatus 
AcDbObject::dwgOut(AcDbDwgFiler* pFiler) const;

Acad::ErrorStatus
AcDbObject::dwgIn(AcDbDwgFiler* pFiler);

Acad::ErrorStatus
AcDbObject::dxfOut(
AcDbDxfFiler* pFiler,
Adesk::Boolean allXdFlag,
Adesk::uchar* regAppTable) const;
Acad::ErrorStatus

AcDbObject::dxfIn(AcDbDxfFiler* pFiler);

Mỗi một hàm nhận biến đầu tiên là một con trỏ đến filer. Một đối tượng AcDbObject ghi dữ liệu và đọc dữ liệu từ filer. Kiểu enum FilerType cho phép bạn kiểm tra kiểu filer, bao gồm các kiểu sau:
  • kFileFiler (used for DWG and DXF files)
  • kCopyFiler
  • kUndoFiler
  • kBagFiler (used with acdbEntMake(), acdbEntMod(), and acdbEntGet())
  • kIdXlateFiler
  • kPageFiler
  • kDeepCloneFiler
  • kIdFiler
  • kPurgeFiler
  • kWBlockCloneFiler
Các hàm dwgOut()dwgIn() sẽ chuyển hướng đến dwgOutFields()dwgInFields(), hay dxfOutFields() và dxfInFields() tương ứng. Nếu dẫn xuất một đối tượng từ AcDbObject, bạn cần phải ghi đè lên các hàm ảo, được sử dụng để lưu trữ liên tục của đối tượng, như quá trình sao chép và UNDO:
  • dwgOutFields()
  • dwgInFields()
  • dxfOutFields()
  • dxfInFields()

Hàm dwgOut()

Hàm dwgOut() goi phương thức dwgOutFields(), được gọi ra bởi các lệnh và điều kiện sau:
  • SAVE (sử dụng kFileFiler)
  • SAVEAS (sử dụng kFileFiler)
  • WBLOCK (sử dụng kWblockCloneFilerkIdXlateFiler)
  • INSERT, XREF (dùng kDeepCloneFilerkIdXlateFiler)
  • COPY (sử dụng cùng filer với INSERT; một sao chép đòi hỏi ghi lại tình trạng của đối tượng và sau đó đọc nó trở lại vào trong đối tượng của cùng một lớp)
  • PURGE (sử dụng kPurgeFiler)
  • Bất cứ lúc nào một đối tượng được phân trang (page in) (sử dụng kPageFiler)
  • Bất cứ lúc nào đối tượng được chỉnh sửa (để ghi lại UNDO; dùng kUndoFiler)

Hàm dwgIn()

Hàm dwgIn(), cái mà gọi phương thức dwgInFields(), được dẫn ra bởi lệnh và điều kiện sau:
  • OPEN (dùng kFileFiler)
  • UNDO (dùng kUndoFiler)
  • INSERT, COPY, XREF (dùng kDeepCloneFilerkIdXlateFiler)
  • WBLOCK (dùng kWblockCloneFilerkIdXlateFiler)
  • Bất cứ lúc nào một đối tượng được phân trang (dùng kPageFiler)

Hàm dxfOut()

Hàm dxfOut(), gọi đến phương thức dxfOutFields(), được dẫn ra bởi lệnh và hàm sau:
  • WBLOCK (dùng kFileFiler)
  • SAVE (dùng kFileFiler)
  • SAVEAS (sử dụng kFileFiler)
  • acdbEntGet() hoặc hàm AutoLISP tương đương (sử dụng kBagFiler)

Hàm dxfIn()

Hàm dxfIn(), gọi dxfInFields(), được dẫn ra bởi lệnh và hàm sau:
  • OPEN (uses kFileFiler)
  • INSERT (uses kFileFiler)
  • acdbEntMod(), acdbEntMake(), acdbEntMakeX(), or the AutoLISP equivalents (use kBagFiler)

Kiểm tra lỗi trong Filer


Khi ghi vào filer, bạn không cần tiến hành quá trình kiểm tra lỗi ngay lập tức. Một khi có lỗi xảy ra, filer trả về cùng một trạng thái lỗi tới tất cả các yêu cầu ghi cho đến khi trạng thái được xóa bỏ bởi filer.
Mọi lớp filer đều có hàm getFilerStatus() trả về trạng thái của filer. Khi đọc một tệp tin, bạn có thể muốn kiểm tra trạng thái filer nếu bạn không chắc chắn bước tiếp theo sẽ thành công hay thất bại.

Dẫn xuất từ lớp AcDbObject

Chương này sẽ trình bày cách để dẫn xuất một lớp mới từ AcDbObject. Nó sẽ cung cấp thông tin chi tiết trong việc filer, bốn kiểu của tham chiếu đối tượng (sở hữu cứng và mềm, con trỏ cứng và con trỏ mềm), và quá trình Undo và redo. Ngoài ra chương này còn thảo luận về cơ cấu cho verioning đối tượng.
Điều kiện là bạn đã làm quen với tài liệu được nó đến trong chương 5, Đối tượng cơ sở dữ liệu và chương 12, Dẫn xuất một đối tượng ObjectARX mới.

Các đề tài trong chuyên mục:

Ghi đè (overridding) các hàm ảo AcDbObject

Nếu sử dụng một lớp dẫn xuất từ AcDbObject, sẽ có rất nhiều các hàm ảo mà bạn phải ghi đè lên như đề cập trong chuyên mục sau. Chuyên mục này sẽ trình bày các hàm thường xuyên được sử dụng và những hàm ít khi dùng đến. 

AcDbObject: Hàm quan trọng để ovenridden

Một lớp mới bắt buộc phải ghi đè lên các hàm sau:
virtual Acad::ErrorStatus
dwgInFields(AcDbDwgFiler* filer);
virtual Acad::ErrorStatus
dwgOutFields(AcDbDwgFiler* filer) const;
virtual Acad::ErrorStatus
dxfInFields(AcDbDxfFiler* filer);
virtual Acad::ErrorStatus
dxfOutFields(AcDbDxfFiler* filer) const;
(~AcDbObject)

AcDbObject: Các hàm thường được overridden

Một lớp mới thường ghi đè các hàm sau:
virtual Acad::ErrorStatus 
audit(AcDbAuditInfo* pAuditInfo);
// Commonly useful, as this happens at a point where a new
// object state is being committed.
//
virtual Acad::ErrorStatus subClose();
// The next two functions apply to container objects.
//
virtual Acad::ErrorStatus
deepClone(AcDbObject* pOwnerObject,
AcDbObject*& pClonedObject,
AcDbIdMapping& idMap,
Adesk::Boolean isPrimary = true) const;
virtual Acad::ErrorStatus
wblockClone(AcRxObject* pOwnerObject,
AcDbObject*& pClonedObject,
AcDbIdMapping& idMap,
Adesk::Boolean isPrimary = true) const;

AcDbObject: Hàm thỉnh thoảng được Overridden

Lớp mới thỉnh thoảng ghi đè lên các hàm sau:
virtual Acad::ErrorStatus 
subErase(Adesk::Boolean erasing);
virtual Acad::ErrorStatus
subHandOverTo(AcDbObject* newObject);
virtual Acad::ErrorStatus
subOpen(AcDb::OpenMode mode);
virtual Acad::ErrorStatus
subCancel();
virtual Acad::ErrorStatus
subSwapIdWith(AcDbObjectId otherId,
Adesk::Boolean swapXdata = false,
Adesk::Boolean swapExtDict = false);

AcDbObject: Hàm hiếm khi được Overridden 

Lớp mới hiếm khi ghi đè các hàm sau:
virtual Acad::ErrorStatus 
setOwnerId(AcDbObjectId objId);
virtual resbuf*
xData(const char* regappName = NULL) const;
virtual Acad::ErrorStatus
setXData(const resbuf* xdata);
virtual void
addPersistentReactor(AcDbObjectId objId);
virtual Acad::ErrorStatus
removePersistentReactor(AcDbObjectId objId);
virtual void
cancelled(const AcDbObject* dbObj);
virtual void
copied(const AcDbObject* dbObj,
const AcDbObject* newObj);
virtual void
erased(const AcDbObject* dbObj,
Adesk::Boolean pErasing = true);
virtual void
goodbye(const AcDbObject* dbObj);
virtual void
openedForModify(const AcDbObject* dbObj);
virtual void
modified(const AcDbObject* dbObj);
virtual void
modifyUndone(const AcDbObject* dbObj);
virtual void
modifiedXData(const AcDbObject* dbObj);
virtual void
unappended(const AcDbObject* dbObj);
virtual void
objectClosed(const AcDbObjectId objId);
virtual void
modifiedGraphics(const AcDbEntity* dbEnt);

AcRxObject: Functions Rarely Overridden

Lớp mới hiếm khi sử dụng các hàm sau:
virtual AcRxObject*       
clone() const;
virtual Acad::ErrorStatus
copyFrom(const AcRxObject* pSrc);
// Do not override; AcDbObject behavior is already accounted for.
//
virtual HRESULT __stdcall
QueryInterface ( REFIID riid,
void ** ppvObject );
virtual ULONG __stdcall
AddRef();
virtual ULONG __stdcall
Release();

AcDbEntity: Hàm để ghi đè (Override)

Nếu thực thi một thực thể mới, xem chương 14, Dẫn xuất từ AcDbEntity, để xem danh sách các hàm để override.

AcDbCurve: Các hàm để Override

Một lớp mới phải ghi đè lên các hàm sau:
virtual Adesk::Boolean    
isClosed() const;
virtual Adesk::Boolean
isPeriodic() const;
virtual Adesk::Boolean
isPlanar() const;
virtual Acad::ErrorStatus
getPlane(AcGePlane&, AcDb::Planarity&) const;
virtual Acad::ErrorStatus
getStartParam(double&) const;
virtual Acad::ErrorStatus
getEndParam(double&) const;
virtual Acad::ErrorStatus
getStartPoint(AcGePoint3d&) const;
virtual Acad::ErrorStatus
getEndPoint(AcGePoint3d&) const;
virtual Acad::ErrorStatus
getPointAtParam(double, AcGePoint3d&) const;
virtual Acad::ErrorStatus
getParamAtPoint(const AcGePoint3d&, double&)const;
virtual Acad::ErrorStatus
getDistAtParam(double param, double& dist) const;
virtual Acad::ErrorStatus
getParamAtDist(double dist, double& param) const;
virtual Acad::ErrorStatus
getDistAtPoint(const AcGePoint3d&, double&) const;
virtual Acad::ErrorStatus
getPointAtDist(double, AcGePoint3d&) const;
virtual Acad::ErrorStatus
getFirstDeriv(
double param,
AcGeVector3d& firstDeriv) const;
virtual Acad::ErrorStatus
getFirstDeriv(
const AcGePoint3d&,
AcGeVector3d& firstDeriv) const;
virtual Acad::ErrorStatus
getSecondDeriv(
double param,
AcGeVector3d& secDeriv) const;
virtual Acad::ErrorStatus
getSecondDeriv(
const AcGePoint3d&,
AcGeVector3d& secDeriv) const;
virtual Acad::ErrorStatus
getClosestPointTo(
const AcGePoint3d& givenPnt,
AcGePoint3d& pointOnCurve,
Adesk::Boolean extend = Adesk::kFalse) const;
virtual Acad::ErrorStatus
getClosestPointTo(
const AcGePoint3d& givenPnt,
const AcGeVector3d& normal,
AcGePoint3d& pointOnCurve,
Adesk::Boolean extend = Adesk::kFalse) const;
virtual Acad::ErrorStatus
getOrthoProjectedCurve(
const AcGePlane&,
AcDbCurve*& projCrv) const;
virtual Acad::ErrorStatus
getProjectedCurve(
const AcGePlane&,
const AcGeVector3d& projDir,
AcDbCurve*& projCrv) const;
virtual Acad::ErrorStatus
getOffsetCurves(
double offsetDist,
AcDbVoidPtrArray& offsetCurves) const;
virtual Acad::ErrorStatus
getSpline(AcDbSpline*& spline) const;
virtual Acad::ErrorStatus
getSplitCurves(
const AcGeDoubleArray& params,
AcDbVoidPtrArray& curveSegments) const;
virtual Acad::ErrorStatus
getSplitCurves(
const AcGePoint3dArray& points,
AcDbVoidPtrArray& curveSegments) const;
virtual Acad::ErrorStatus
extend(double newParam);
virtual Acad::ErrorStatus
extend(
Adesk::Boolean extendStart,
const AcGePoint3d& toPoint);
virtual Acad::ErrorStatus
getArea(double&) const;

Implementing Member Functions 

Khi định nghĩa một hàm thành viên mới hoặ cghi đè lên hàm cũ, đầu tiên cần phải gọi tới hàm assertReadEnabled(), assertWriteEnabled(), hoặc assertNotifyEnabled() để chắc chắn rằng đối tượng đang được mở ở trạng thái thích hợp. Trong 3 hàm này, assertWriteEnabled() là tối quan trọng. Bạn có thể sử dụng nó để điều khiển UNDO ghi chép lại sự thay đổi xảy ra trong các hàm thành viên. (Tìm hiểu thêm Undo và Redo.) Thậm chí bạn không cần phải ghi chép để UNDO, nhưng vẫn nhất thiết phải gọi đến hàm:
assertWriteEnabled(kFalse, kFalse);
Lời gọi hàm này đánh dấu đối tượng chưa được lưu lại. Nếu không theo chỉ dẫn này, có thể dẫn đến kết quả hư hỏng bản vẽ.
Bảng sau đây trình bày 3 trạng thái có thể xảy ra cho quá trình mở đối tượng (đọc, ghi và ghi chú) và chỉ ra hàm xác nhận assert nào thành công cho mỗi trạng thái. Nếu đối tượng không được mở đúng trạng thái cho hàm xác nhận, thì hàm sẽ không trả về. AutoCAD® thoát ra và người dùng đuọc nhắc về việc có lưu bản vẽ lại hay không.

Dẫn xuất một lớp ObjectARX mới

Chuyên mục này sẽ miêu tả cách tổ chức mã nguồn và sử dụng macro ObjectARX® để làm đơn giản công việc dẫn xuất một lớp ObjectARX mới. Các macro cho phép một lớp mới được tham gia vào cơ cấu định kiểu chạy bên trong AcRxObject (AcRxObject runtime type identification mechanism). Nếu bạn không muốn phân biệt lớp mới trong lúc chạy, bạn có thể sử dụng kiểu dẫn xuất từ C++ để tạo ra một lớp mới. 

Các đề tài trong chuyên mục:

Lớp dẫn xuất mới

Để tạo điều kiện cho phát triển, các lớp đối tượng mới nên được khai báo và thực thi trong các module ObjectDBX riêng biệt. Module này chứa phần cơ sở dữ liệu (CSDL) của ứng dụng, bao gồm các macro ObjectDBX được miêu tả trong chuyên mục này và các hàm chồng từ các lớp ObjectARX khác cũng như các hàm xác định khác trong lớp. Bởi một ứng dụng ObjectDBX, module này có thể không cần sử dụng đến trình soạn thảo của AutoCAD® editor, reactor editor, hoặc bất kể API cụ thể nào khác cho ứng dụng máy chủ AutoCAD. Bạn nên cung cấp một đối tượng enabler cho đối tượng mới của mình, xem chương 24, Object Enablers để biết thêm chi tiết.
ObjectARX cung cấp một tập hợp macro, đã được khai báo trong têm tin rxboiler, sẽ giúp bạn tạo ra những lớp dãn xuất từ AcRxObject. Nếu không sử dụng macro để định nghĩa một lớp mới, lớp đó sẽ kết thừa các identity thực thi của lớp ObjectARX cơ sở liền trên.

Ứng dụng không nên sử dụng dẫn xuất từ các lớp sau:
  • AcDbDimension
  • AcDbSymbolTable, AcDbSymbolTableRecord, and all classes derived from them
  • AcDbBlockBegin
  • AcDbBlockEnd
  • AcDbSequenceEnd
  • AcDb2dPolyline
  • AcDb2dVertex
  • AcDb3dPolyline
  • AcDb3dPolylineVertex
  • AcDbPolygonMesh
  • AcDbPolygonMeshVertex
  • AcDbPolyFaceMesh
  • AcDbPolyFaceMeshVertex
  • AcDbFaceRecord
  • AcDbViewport
  • AcDbMInsertBlock
  • AcDbVertex

Xác định lớp thời gian chạy (Runtime Class Identification RTTI)

Mọi lớp trong phân cấp ObjectARX dẫn xuất từ AcRxObject đều có một đối tượng descriptor tương ứng. Nó là một đại diện của  AcRxClass chịu trách nhiệm giữ thông tin cho RTTI. Lớp descriptor gpDesc, là một thành viên data tĩnh của lớp. Ví dụ: AcDbEllipse::gpDesc. Đối tượng descriptor lớp được tạo ra trong lúc khởi tạo, khi các lớp đã được đăng ký với ObjectARX và được thêm vào từ điển cấp hệ thống, acrxClassDictionary. Macro được miêu tả ở đây giúp cho khai báo và thực thi của các hàm nhất định có liên quan đến runtime identification và hàm khởi tạo. Chúng bao gồm routime khởi tạo như là các hàm desc(), cast(), isKindOf(), và isA() cho đối tượng mới.
Các hàm quan trọng cung cấp bởi lớp AcRxObject cho RTTI bao gồm:
  • desc(), hàm thành viên tĩnh, trả về đối tượng descriptor lớp của lớp đặc biệt.
  • cast(), hàm thành viên tĩnh,trả về một đối tượng của kiểu xác định, hoặc NULL nếu đối tượng không đúng như yêu cầu hoặc là một lớp dấn xuất.
  • isKindOf() trả về khi đối tượng thuộc về một lớp xác định (hoặc lớp dẫn xuất).
  • isA() trả về đối tượng descriptor lớp của một đối tượng.
Khi muốn biết đối tượng thuộc lớp nào, hãy sử dụng AcRxObject::isA(). Hàm này trả về đối tượng descriptor (một đại diện của AcRxClass) cho một đối tượng CSDL. Khai báo của nó là:
AcRxClass* isA() const;

Khi đã biết được lớp của đối tượng, bạn có thể sử dụng hàm desc() để lấy đối tượng descriptor.


static AcRxClass* desc();
Ví dụ sau đây tìm kiếm đại diện của AcDbEllipse hoặc bất cứ lớp dẫn xuất nào từ nó, sử dụng hàm thành viên isKindOf()AcDbEllipse::desc():
AcDbEntity* curEntity = somehowGetAndOpenAnEntity();
if (curEntity->isKindOf(AcDbEllipse::desc())) {
   // Got some kind of AcDbEllipse instance.
}
Ví dụ này trình bày một cách khác để tìm kiếm đại diện của AcDbEllipse hoặc các lớp dẫn xuất bằng cách sử dụng hàm thành viên AcDbEllipse::cast():
AcDbEllipse* ellipseEntity = AcDbEllipse::cast(curEntity);
if (ellipseEntity != NULL) {
   // Got some kind of AcDbEllipse instance.
}
Ví dụ sau lại tìm kiếm đại diện của AcDbEllisp, nhưng loại trừ các lớp dẫn xuất, sử dụng các hàm isA()AcDbEllipse::desc():
if (curEntity->isA() == AcDbEllipse::desc()) {
   // Got an AcDbEllipse, no more, no less.

Macro khai báo lớp

Tệp tin header cho đối tượng mới có thể sử dụng macro ObjectARX ACRX_DECLARE_MEMBERS(CLASS_NAME) để khai báo các hàm desc(), cast(), và isA().
Macro được dùng trong khu vực public của khai báo lớp mới:
class myClass : public AcRxObject
{
public:
ACRX_DECLARE_MEMBERS(myClass);
...
};
For AsdkPoly, the following line expands to a single long line of code.
ACRX_DECLARE_MEMBERS(AsdkPoly);
When reformatted to multiple lines for clarity, the line looks like this:
virtual AcRxClass* isA() const;
static AcRxClass* gpDesc;
static AcRxClass* desc();
static AsdkPoly* cast(const AcRxObject* inPtr)
{
    return ((inPtr == 0)
        || !inPtr->isKindOf(AsdkPoly::desc()))
        ? 0 : (AsdkPoly*)inPtr;
};
static void rxInit();
Hàm tĩnh rxInit() và con trỏ tĩnh gpDesc được khai báo bởi macro được dùng để thực thi các hàm isA(), desc(), và cast()

Macro thực thi lớp

Để thực thi một lớp mới, sử dụng một trong ba macro trong tệp tin nguồn (source):
  • ACRX_NO_CONS_DEFINE_MEMBERS(CLASS_NAME, PARENT_CLASS)
Sử dụng cho khai  lớp vắn tắt hoặc bất cứ lớp nào không cần khởi tạo.
  • ACRX_CONS_DEFINE_MEMBERS(CLASS_NAME, PARENT_CLASS, VERNO)
Sử dụng cho các lớp nhất có thể được khởi tạo nhưng không được ghi vào trong tệp tin.
  • ACRX_DXF_DEFINE_MEMBERS(CLASS_NAME, PARENT_CLASS, DWG_VERSION,\
    MAINTENANCE_VERSION, PROXY_FLAGS, DXF_NAME, APP)
Sử dụng cho các lớp có thể được ghi, đọc từ tệp tin DWG, DXF. Mỗi một macro định nghĩa như sau: 
  • Đối tượng descriptor lớp
  • Hàm khởi tạo lớp (xem Class Initialization Function)
  • Hàm desc() function cho lớp
  • Hàm ảo isA() (thừa kế từ AcRxObject) mà lớp mới này sẽ ghi đè.
Với đối tượng AsdkPoly, dưới đây là một ví dụ:
ACRX_DXF_DEFINE_MEMBERS(AsdkPoly, AcDbCurve, AcDb::kDHL_CURRENT,\
    AcDb::kMReleaseCurrent, 0, POLYGON, /*MSG0*/"AutoCAD");
Khi định dạng lại thành nhiều đoạn thẳng cho rõ ràng hơn, đoạn thẳng nhìn giống như sau:
AcRxClass* AsdkPoly::desc()
{
    if (AsdkPoly::gpDesc != 0)
        return AsdkPoly::gpDesc;
    return AsdkPoly::gpDesc =
        (AcRxClass*)((AcRxDictionary*)acrxSysRegistry()->
        at("ClassDictionary"))->at("AsdkPoly");
}
AcRxClass* AsdkPoly::isA() const
{
    return AsdkPoly::desc();
}
AcRxClass* AsdkPoly::gpDesc = 0;
static AcRxObject * makeAsdkPoly()
{
    return new AsdkPoly();
}
void AsdkPoly::rxInit()
{
    if (AsdkPoly::gpDesc != 0)
        return;
    AsdkPoly::gpDesc = newAcRxClass("AsdkPoly",
        "AsdkCurve", AcDb::kDHL_CURRENT, AcDb::kMReleaseCurrent,
         0, &makeAsdkPoly, "POLYGON", "\"AutoCAD\"");
};
Khi mở rộng ra, dấu chấm phảy (;) cuối cùng của macro gọi dòng dịch chuyển vào sau khi đóng ngoặc nhọn (}) cho một định nghĩa hàm. Vì vậy, dấu chấm phảy không được đòi hỏi cho macro này gọi dòng.
Nếu muốn viết hàm rxInit() cho riêng mình,  sử dụng macro ACRX_DEFINE_MEMBERS(), nó sẽ định nghĩa desc(), cast() và isA() cho lớp nhưng không định nghĩa hàm rxInit(). Macro này cũng không tạo ra đối tượng AcRxClass mong muốn mà là trách nhiệm của hàm rxInit().

Hàm khởi tạo lớp

Hàm khởi tạo cho mỗi lớp là rxInit(). Một ứng dụng sẽ định nghĩa một lớp riêng phải gọi ra hàm này trong quá trình khởi tạo.
Hàm này được định nghĩa tự động bởi từng hàm trong số 3 macro ACRX_xxx_DEFINE_MEMBERS() và thực hiện các nhiệm vụ sau:
  • Đăng ký lớp mới
  • Tạo ra đối tượng mô tả lớp
  • Đặt bộ mô tả vào trong lớp từ điển dictionary
Nếu muốn định nghĩa riêng hàm rxInit(), hãy sử dụng macro ACRX_DEFINE_MEMBERS().

Thư viện AutoLISP mở rộng Doslib (Phần 2)

Các hàm thiết lập -------------------------------------------------------------------------------- dos_getini Trả về giá trị từ tệp tin INI ...