Giới thiệu
Trong bài trước, bạn đã biết cách dẫn xuất từ AcDbObject để tạo ra một thực loại đối tượng mới mạnh mẽ và cách lưu trữ chúng trong NOD. Trong bài này, bạn sẽ được tìm hiểu về cách chúng ta có thể tạo ra một thực thể mới, dẫn xuất từ AcDbEntity hoặc một lớp dẫn xuất từ nó. Từ đó, bạn sẽ có một cách nhìn khác về sức mạnh thực sự của một ứng dụng ObjectARX là như thế nào!
Phiên bản khởi nguồn của SDK ObjectARX trên AutoCAD 13 đã mở ra một thế giới mới cho những nhà phát triển và cho chính bản thân AutoDesk. Thực thể mới sẽ cho phép nhà lập trình xây dựng những ứng dụng đồ họa phong phú và thể hiện được nhiều tính năng mới không có trong những thực thể AutoCAD nguyên bản. AutoDesk cũng bắt đầu phát triển những ứng dụng riêng của họ dựa trên những loại thực thể mới được tạo ra, giúp mang đến những chức năng tuyệt vời, những trải nghiệm thú vị mà bạn chưa từng biết đến.
Khi bạn nghĩ về một thực thể mới, trước tiên bạn cần nghĩ đến việc sử dụng nó như thế nào, quản lý, chỉnh sửa và mọi tính năng nó thể hện trước người dùng. Việc này cho phép bạn có thể hoạch định hành vi của thực thể và liệt kê mọi công việc nó sẽ có thể hỗ trợ và tiến hành. Bước này hết sức quan trọng giúp quyết định khi nào nên sử dụng thực thể AutoCAD nguyên bản được thêm dữ liệu XData hoặc XRecords, và khi nào nên sử dụng một thực thể mới.
Mặt khác, khi sử dụng thực thể AutoCAD chuẩn, bạn cũng cần điều khiển được hành vi của chúng để hỗ trợ và quản lý mọi ứng xử của người dùng người dùng. Việc này không hề đơn giản và người dùng càng sáng tạo thì bạn sẽ càng mất thời gian. Khi bạn phát triển một thực thể mới độc lập (không dẫn xuất), nó sẽ mang lại cho bạn nhiều lợi thế trong việc điều khiển tiến trình của người dùng, nhưng nó cũng cung gây cho bạn không ít khó khăn.
Lợi thế của việc sử dụng một thực thể mới
Rất nhiều lợi thế bạn sẽ có được với một thực thể mới. Tôi muốn liệt kê một trong số những lợi thế đó chỉ để giúp bạn bạn nhận ra nó mạnh đến như thế nào:
Đồ họa mới (Custom graphics): Khi tạo một thực thể mới, bạn cũng có trách nhiệm tạo ra đồ họa của nó. ObjectARX cung cấp một vài hàm vẽ nguyên thủy cho phép bạn vẽ và sử dụng mọi thứ bạn cần. Đừng lo lắng nếu thực thể của bạn quá đơn giản hay quá phức tạp, ObjectARX cung cấp mọi công cụ để bạn có thể làm được.
Bắt điểm và điểm nút mới (Custom Grips and OSNAPs): Nó cũng cung cấp cho bạn thực thi hoặc không sử dụng lưới và chức năng bắt điểm (Object Snap). Bạn có sẽ cần một vài điểm OSNAP xác định và hàm chức năng lưới (Grip), đừng ngần ngại, hãy sử dụng nó!
Biến đổi riêng: Một thực thể mới có thể được biến đổi theo những tiêu chuẩn riêng của bạn. Nó có được đầy đủ lợi thể của thư viện AcGe bao hàm trong OjbjectARX.
Nhúng thực thể nguyên bản (Embedded native entities): Nếu bạn muốn xây dựng một thực thể mới nhìn giống những thực thể nguyên bản, bạn hoàn toàn có thể nhúng những thực thể gốc vào trong trong thực thể mới và và lấy được tính năng đã có của chúng. Giả sử bạn cần xây dựng một thực thể mới giống Polyline với hatch ở bên trong. Nó có thể dễ dàng thực hiện bằng cách nhúng một thực thể AcDbHatch vào trong thực thể dẫn xuất AcDbPolyline.
Đồ họa thực thể (Custom entity graphics)
Thư viện AcGi cung cấp mọi thứ cần thiết để xây dựng đồ họa cho thực thể của bạn. Thực thể đó sẽ được thể hiện trên màn hình AutoCAD bằng một trong hai phương thức cơ bản sau:virtual Adesk::Boolean
AcDbEntity::worldDraw (AcGiWorldDraw * pWd);
virtual void
AcDbEntity::viewportDraw (AcGiViewportDraw * pVd);
Hàm đầu tiên là worldDraw() chịu trách nhiệm vẽ hình khối chuẩn cho thực thể. Hàm thứ hai là viewportDraw() có thể tùy chọn, nó cho phép bạn vẽ hình khối liên quan trên viewport. Hai hàm này sẽ nhận con trỏ ngữ cảnh vẽ,cho phép bạn tiến hành vẽ.
Vì một vài lý do mà các hàm này sẽ được gọi đến một vài lần trong khi chạy. Bạn cần phải cung cấp đoạn mã nhanh và hiệu quả nhất có thể. Đừng thực hiện công việc tính toán nặng nề, vòng lặp dài và tốn thời gian ở đây. Thực thể mới được tạo nên từ các thực thể nguyên bản thì không được khai báo chúng bên trong hàm. Hãy khai báo chúng như những thành viên của lớp và chỉ cần chuyển hướng gọi hàm bên trong worldDraw() hoặc viewportDraw() tới những thực thể nhúng này.
AutoCAD tiến hành quá trình vẽ thông mọi thực thể cơ sở dữ liệu và gọi phương thức worldDraw() đầu tiên. Nếu phương thức worldDraw() trả về Adesk::kFalse, AutoCAD cũng sẽ hướng đến các viewport và gọi phương thức viewportDraw() của thực thể. Chính xác là lúc này bạn có thể vẽ hình khối khác nhau dựa trên định dạnh của mỗi viewport.
Các chức năng vẽ nguyên thủy hết sức đơn giản, chỉ bao gồm: Circle, Circular arc, Polyline, Polygon, Mesh, Shell, Text, Xline và Ray. Xin hãy tham khảo tài liệu AcGi trong SDK ObjectARX để thấy được chỉ dẫn và thông tin chi tiết về cách sử dụng. Tất cả chúng đều được gọi đến thông qua hàm geometry().
Thực thể mới cũng cho phép bạn chia nhỏ chúng hơn nữa. Chức năng này được thực hiện bằng cách dùng đối tượng AcGiSubEntityTraits. Đối tượng này cài đặt các giá trị thông qua các hàm đặc tính:
Color
Layer
Linetype
Kiểu điền đa tuyến
Đánh dấu lựa chọn
Bằng cách này, bạn có thể chia các thông tin lô gics thông qua đồ họa của nó. Ví dụ, nếu thực thể có một vài text và một vài đoạn thẳng line, có thể bạn sẽ cần chia chúng vào hai tiểu nhóm thực thể. Xa hơn, nếu bạn muốn vẽ một phần thực thể dựa bằng màu hoặc đường nét xác định, nó cũng được thực hiện bằng các tiểu nhóm này. Mỗi tiểu thực thể đều có một đánh dấu riêng. Nó cho phép bạn tiến hành tương tác với người dùng bằng cách tìm ra tiểu thực thể nào đã được click. Nếu bạn muốn làm vậy, trước khi vẽ hình khối của tiểu thực thể, hãy gọi hàm setSelectionMarker() thông qua chỉ số tăng dần.
Trong hàm viewportDraw(), bạn phải truy cập đến
Dưới đây là một vài ví dụ của cả phương thức wordDraw() và viewportDraw():
Đối tượng hình học viewport cung cấp
Inside viewportDraw() function you will also have access to caller viewport information through AcGiViewport object inside the AcGiViewportDraw object passed in. The viewport geometry object provides the same primitives world geometry object plus the following polygon and polyline primitives, which use eye and display space coordinates: polylineEye, polygonEye, polylineDc and polygonDc. Some examples of both worldDraw() and viewportDraw() methods are presented below:
Adesk::Boolean MyEnt::worldDraw(AcGiWorldDraw *pW) {
AcGePoint3d verts[5];
// Create some random points
verts[0] = verts[4] = AcGePoint3d(-0.5, -0.5, 0.0);
verts[1] = AcGePoint3d( 0.5, -0.5, 0.0);
verts[2] = AcGePoint3d( 0.5, 0.5, 0.0);
verts[3] = AcGePoint3d(-0.5, 0.5, 0.0);
// Set the subentity color as 3
pW->subEntityTraits().setColor(3);
// Draw the polyline primitive
pW->geometry().polyline(5, verts);
return Adesk::kTrue;
}
void MyEnt::viewportDraw(AcGiViewportDraw* pV){
AcGePoint2d lleft, uright;
// Get viewport's DC coordinates
pV->viewport().getViewportDcCorners(lleft,uright);
// Perform some math here
double xsize = uright.x - lleft.x;
double ysize = uright.y - lleft.y;
xsize /= 10.0;
ysize /= 10.0;
double xcenter = uright.x - xsize;
double ycenter = uright.y - ysize;
double hxsize = xsize / 2.0;
double hysize = ysize / 2.0;
AcGePoint3d verts[5];
// Set vertex initial value
for (int i=0; i<5; i++) {
verts[i].x = xcenter;
verts[i].y = ycenter;
verts[i].z = 0.0;
}
// Perform some adjustments
verts[0].x -= hxsize;
verts[0].y += hysize;
verts[1].x += hxsize;
verts[1].y += hysize;
verts[2].x += hxsize;
verts[2].y -= hysize;
verts[3].x -= hxsize;
verts[3].y -= hysize;
verts[4] = verts[0];
// Set the subentity color as 3
pV->subEntityTraits().setColor(3);
// Draw the polyline on DC context
pV->geometry().polylineDc(5, verts);
} Thực thi bắt điểm (OSNAP)
Thực thể của bạn cũng cần cung cấp một vài tiêu điểm thông qua tính năng bắt điểm. Tùy thuộc vào độ phức tạp của thực thể mà bạn cần phải thực thi một vài điểm OSNAP và một số cách bắt điểm như EndPoint, Center, ... Để đưa tính năng OSNAP vào trong thực thể, bạn sẽ cần thêm phương thức sau vào trong lớp (Có rất nhiều đăng ký khác nhau):virtual Acad::ErrorStatus AcDbEntity::getOsnapPoints(
AcDb::OsnapMode osnapMode,
int gsSelectionMark,
const AcGePoint3d& pickPoint,
const AcGePoint3d& lastPoint,
const AcGeMatrix3d& viewXform,
AcGePoint3dArray& snapPoints,
AcDbIntArray& geomIds) const;
Hàm sau sẽ giúp bạn điền các thông số vào mảng AcGePoint3dArray với những điểm phù hợp với phương thức bắt điểm. Các giá trị có thể xảy ra là:
AcDb::kOsModeEnd: điểu kết thúc trên thực thể gần điểm pickpoint nhất.
AcDb::kOsModeMid: trung điểm (của đoạn thẳng, cung tròn, ...) gần điểm pickpoint nhất.
AcDb::kOsModeCen: tâm điểm (của hình tròn hoặc cung tròn) gần điểm pickpoint nhất.
AcDb::kOsModeNode: điểm node gần pickpoint nhất.
AcDb::kOsModeQuad: điểm phần tư gần pickpoint nhất.
AcDb::kOsModeIns: điểm chèn của thực thể (của BlockReference hoặc đối tượng MText).
AcDb::kOsModePerp: giao điểm của thực thể với đường thẳng vuông góc với nó qua điểm lastPoint.
AcDb::kOsModeTan: điểm nằm trên thực thể nơi đường thẳng đi qua điểm lastPoint sẽ tiếp xúc với thực thể.
AcDb::kOsModeNear: Tìm điểm gần nhất với điểm pickPoint.
Tưởng tượng thực thể mới là một hình chữ nhật và người dùng chạy lệnh LINE muốn vẽ từ điểm EndPoint. Thực thể cần phải trả lời AutoCAD bằng cách cung cấp điểm EndPoint (nếu có). Trong trường hợp này, trong hàm getOsnapPoints() sẽ cần điền vào AcGePoint3dArray các điểm góc của hình chữ nhật. AutoCAD sẽ tìm các điểm nằm trong khung hình, và và gần con trỏ (cursor) nhất. Vì vậy hàm sẽ giống như sau:
Acad::ErrorStatus MyEnt::getOsnapPoints(
AcDb::OsnapMode osnapMode,
int gsSelectionMark,
const AcGePoint3d& pickPoint,
const AcGePoint3d& lastPoint,
const AcGeMatrix3d& viewXform,
AcGePoint3dArray& snapPoints,
AcDbIntArray& geomIds) const {
assertReadEnabled();
switch (osnapMode) {
case AcDb::kOsModeEnd:
snapPoints.append(pt[0]);
snapPoints.append(pt[1]);
snapPoints.append(pt[2]);
snapPoints.append(pt[3]);
break;
}
return Acad::eOk;
}
Điểm giao (intersection OSNAP) lại không có được thông qua phương thức getObjectSnap(). Vì rất phức tạp nên có một hàm đặc biệt gọi là intersectWith() đảm trách việc đó. Tôi không muốn giới thiệu chi tiết ở đây nhưng bạn có thể đọc và tìm hiểu thêm thông tin trong tài liều SDK.
Thực thi nắm GRIP và Kéo giãn điểm (GRIP and Stretch)
Phương thức nắm điểm (GRIP) cung cấp một cách thức đơn giản và tuyệt vời cho người dùng để chỉnh sửa và biến đổi các thực thể. Bạn có thể sẽ muốn bổ sung tính năng này trong thực thể của mình. Xa hơn, kéo dãn (stretch) điểm sẽ cho phép người dùng kéo dãn thực thể. Hai tính năng này có thể được bổ sung hết sức đơn giản, làm cho thực thể của bạn thêm linh hoạt và mạnh mẽ.Bạn chỉ cần cho biết điểm có bản (key point) làm điểm nắm Grip và Stretch. Những hàm khác sẽ chịu trách nhiệm trên tập hợp hành vi của thực thể khi mỗi điểm Grip và Stretch được sử dụng. Thực thể càng phức tạp thì chương trình càng phức tạp đôi chút. Với tính năng nắm điểm lưới Grip, bạn cần bổ sung một cặp hàm. Đầu tiên là hàm getGripPoints(), sẽ trả về những điểm bạn muốn kích hoạt chức năng điểm lưới. Hàm thứ hai gọi là moveGripPointsAt() sẽ thực hiện hành động khi điểm lưới bị kích hoạt. virtual Acad::ErrorStatus
AcDbEntity::getGripPoints (AcGePoint3dArray& gripPoints,
AcDbIntArray& osnapModes,AcDbIntArray& geomIds) const;
virtual Acad::ErrorStatus
AcDbEntity::moveGripPointsAt (const AcDbIntArray& indices,
const AcGeVector3d& offset);
Nhớ rằng điểm lưới không bắt buộc phải nằm trên các hình khối. Bạn có thể tạo điểm lưới từ bất kỳ đâu, ví dụ như tâm của hình chữ nhật - nơi chẳng có hình khối nào.
Hàm getGripPoints() nhận 3 tham số. Chỉ tham số đầu tiên là đã được sử dụng. Đó là mảng điểm 3D được cung cấp bởi AutoCAD. Mảng này chứa mọi điểm liên quan đến quá trình nắm điểm lưới Grip. Như nhiều thực thể có liên quan đến quá trình này, mảng có thể đã được điền sẵn. Bạn chỉ cần thêm các điểm mong muốn vào mảng. Điểm thêm vào trong hàm getGripPoints() sẽ được xác định bằng chỉ số theo thứ tự.
Hàm moveGripPointsAt() sẽ nhận thứ tự mảng và vector 3D gửi bởi AutoCAD với quá trình biến đổi (AcGeVector3d) được áp dụng. Lần này bạn cần lặp trong mảng điểm, lấy từng giá trị (chỉ số), tùy thuộc vào giá trị khởi động quá trình biến đổi tại điểm mong muốn. Hình dung lần nữa rằng thực thể HCN của bạn có 5 điểm Grip, từng góc của HCN và điểm tâm. Với từng góc thì chỉ áp dụng biến đổi với điểm grip của nó, còn với điểm tâm bạn sẽ làm biến đổi cho toàn bộ mọi điểm. Quá trình nắm điểm góc sẽ tạo ra kết quả kéo dãn HCN tại góc và khi nằm điểm tâm, sẽ làm cho toàn bộ thực thể di chuyển. Để áp dụng biến đối với từng điểm, hãy gọi phương thức transformBy() với tham số vector AcGeVector3d nhận được từ AutoCAD.
Mặt khác, kéo điểm được định nghĩa và điều khiển bởi hai hàm. Chúng rất giống với chức năng điểm lưới Grip và đôi khi bạn chỉ cần trả về bằng cách gọi chức năng Grip bên trong hàm Stretch tương ứng:
virtual Acad::ErrorStatusAcDbEntity::getStretchPoints(
AcGePoint3dArray& stretchPoints) const;
virtual Acad::ErrorStatus
AcDbEntity::moveStretchPointsAt(
const AcDbIntArray& indices, const AcGeVector3d& offset);
Hành vi của hàm kéo giãn tương tự giống với hàm Grip. Khi stretch một điểm, bạn chỉ cần chuyển hướng gọi hàm như sau:
Acad::ErrorStatus MyEnt::getStretchPoints(
AcGePoint3dArray& stretchPoints) const {
AcDbIntArray osnapModes,geomIds;
return MyEnt::getGripPoints(stretchPoints,osnapModes,geomIds);}
Acad::ErrorStatus MyEnt::moveStretchPointsAt(
const AcDbIntArray& indices, const AcGeVector3d& offset) {return MyEnt::moveGripPointsAt(indices,offset);}
Tương tự với khái niệm của hàm worldDraw() và viewportDraw() cũng được áp dụng vào các hàm moveGripPointAt() và moveStretchPointsAt(). Nó được gọi đến vài lần và chúng cần được thực hiện nhanh nhất có thể. Khi click vào một nút Grip, hàm moveGripPoints() sẽ được gọi tương ứng với sự di chuyển chuột nhỏ nhất.
Thực hiện một biến đổi
Thực thể mới sẽ cần hỗ trợ cho các phép biến đổi nếu bạn muốn cho phép người dùng tiến hành lệnh giống như MOVE, ROTATE và SCALE. Tính năng này được bổ sung thông qua phương thức transformBy(), nhận một ma trận biến đổi đại diện cho phép biến đối hiện hành được áp dụng với thực thể. Trong hàm, bạn sẽ áp dụng ma trận này vào dữ liệu của thực thể để mang lại các thay đổi. Lớp AcGeMatrix3d hỗ trợ tất cả các kiểu của biến đổi và đóng gói chúng vào trong một ma trận.
virtual Acad::ErrorStatus
AcDbEntity::transformBy(const AcGeMatrix3d& xform);
Thực thi điển hình của hàm transformBy() như sau:Acad::ErrorStatus MyEnt::transformBy(
const AcGeMatrix3d& xform) {
pt[0].transformBy(xform);
pt[1].transformBy(xform);
pt[2].transformBy(xform);
pt[3].transformBy(xform);
}
Trong một vài trường hợp đặc biệt, bạn sẽ cần áp dụng biến đổi tới một tiêu bản hoặc bản sao của thực thể gốc. Thực hiện bằng phương thức getTransformedCopy() nhận ma trận biến đổi vafcon trỏ được lấp đầy với bản sao đã biến đổi của thực thể.
Cần nhiều thông tin hơn nữa? Trong bài tới, tôi sẽ trình bày một ví dụ thực hành ngắn gọn với một thực thể mới. Hãy tiếp tục theo dõi!