June 30, 2013

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().

Hàm toàn cục acedCommand

int acedCommand(int rtype, ... unnamed);

rtype Thông số đầu tiên trong mỗi cặp đại diện cho kiểu dữ liệu của thông số đi liền kề; một mã kiểu kết quả được định nghĩa trong adscodes.h
unnamed Thông số thứ hai trong mỗi cặp mang dữ liệu để đưa vào lệnh hoặc 0 (hay RTNONE) để chỉ ra kết thúc các cặp hệ số.

Thực hiện một hoặc nhiều lệnh AutoCAD


Note The acedCommand() function supports re-entrance up to four levels deep. AutoLISP does not support re-entrance, so if acedCommand() is called from within a function that has been executed via the AutoLISP/acedDefun() interface, there is no need to worry about re-entering acedCommand(). Commands registered via acedRegCmds->addCommand(), however, do support re-entrance and there is no way to determine how many levels of re-entrance are currently in effect to avoid exceeding the limit of four levels. If you must use acedCommand() in functions registered via acedRegCmds->addCommand(), you can avoid exceeding the re-entry limit of four levels, if you:

Do not use acedCommand() in functions registered as ACRX_CMD_TRANSPARENT
Do not use acedCommand() to execute any other commands registered via acedRegCmds->addCommand()
Do not use acedCommand() to execute anything that will allow user interaction
The acedCommand() function has a variable-length argument list. Arguments to acedCommand() are treated as pairs. The first of each pair identifies the type of the argument that follows, and the second contains the actual data. The final argument in the list must be a single argument whose value is either 0 or RTNONE. If 0 or RTNONE is the only argument in the list, this is equivalent to pressing [Ctrl]+[C] on the keyboard. Such a call cancels most AutoCAD commands (if any are in progress or are pausing for input).

A string that contains a single backslash ("\\" in source) causes the command to pause for user input, as described in the section “Pausing for User Input” later in this topic. An empty string ("") or null string is equivalent to a space entered from the keyboard.

The following restrictions apply to this function:

You cannot call acedCommand() to invoke AutoLISP functions nor other ARX applications (external functions in other ARX applications can be invoked with acedInvoke()).
You cannot call acedCommand() to invoke AutoCAD commands that directly access peripheral devices. These commands include DTEXT, SKETCH, and PLOT.
You can call acedCommand() to invoke SCRIPT or RSCRIPT (unlike the AutoLISP (command)).

Note If you call acedCommand() or acedCmd() to invoke SCRIPT or RSCRIPT, the script file is not read until the application returns control to AutoLISP. Therefore, such a call should be the external function's last call to an ARX library function; otherwise, the results can be confusing.

Nếu biến hệ thống AutoCAD CMDECHO được đặt bằng 0, dữ liệu được gửi bởi acedCommand() sẽ không được hiển thị. Ngược lại, CMDECHO = 1 thì cả lệnh và dữ liệu của nó sẽ được hiển thị trên dòng nhắc mà thôi.

Hàm acedCommand() có thể khởi động lệnh SAVE, SAVEAS của AutoCAD. Khi nó diễn ra, AutoLISp đưa ra thông điệp kSaveMsg tới tất cả các ứng dụng ARX đang được load, ngoại trừ ứng dụng đang gọi lệnh SAVE.

Giá trị trả về của acedCommand() không chỉ ra được hàm thành công hay thất bại, mà thường xuyên là RTNORM. Nó có thể trả về RTCAN nếu người dùng hủy bỏ lệnh bằng cách nhấn [Ctrl]+[C] hay [Ctrl]+[Break]. Nó trả về RTERROR hoặc RTREJ để chỉ ra thất bại của không rõ nguồn, chứ không phải của lệnh hiện hành.

Avoiding Problems with NEW and OPEN

Để tránh mất đồng bộ giao diện truyền tin giữa AutoCAD và ARX, không được đưa vào acedCommand() hay acedCmd() các lệnh NEW hoặc OPEN. Thay vào đó, đưa ra các đoạnh mã thực thi chức năng NEW và OPEN, trả lời tới dòng nhắc và gọi hàm trong ứng dụng ARX để trả lại quyền kiểm soát cho ưng dụng ARX.

Tạm dừng cho người dùng nhập liệu

Nếu một lệnh AutoCAD đang diễn ra và AutoCAD yêu cầu ký hiệu PAUSE như là một tham số cho hàm acedCommand() hoặc acedCmd(), lệnh sẽ bị đình chỉ để cho phép người dùng nhập liệu trực tiếp, bao gồm cả gắp thả (dragging). Ký hiệu PAUSE thay thể cho một ký tự "\". Nó giống như dấu gạch ngược tạm dừng cơ cấu cung cấp các menu.

Nếu bạn sử dụng một lệnh minh vô hình (transparent) trong khi lệnh đang bị tạm dừng, lệnh ban đầu sẽ bị treo trong khi lệnh transparent diễn ra. Vì vậy, người dùng vẫn có thể zoom và pan trong khi acedCommand() tạm dừng. Tạm dừng vẫn có hiệu lực cho đến khi AutoCAD có được đầu vào hợp lệ và không có lệnh transparent nào khác đang diễn ra..

Nếu AutoCAD tạm dừng khi một lệnh chờ đợi nhập vào một chuỗi ký tự, hay một giá trị attribute, AutoCAD sẽ chỉ dừng lại chỉ khi biến hệ thống TEXTVAL được đặt khác 0. Ngược lại, giá trị của ký tự tạm dừng (dấu sổ ngược) sẽ được thay thế cho text và không tạo ra quá trình tạm dừng nữa.

Khi lệnh acedCommand() tạm dừng cho người nhập liệu, hàm vẫn đang được kích hoạt, vì vậy người dùng không thể nhập một biểu thức AutoLISP để tính giá trị.

Tạo dừng không  đình chỉ đầu vào thực đơn. Nếu một thành phần trong menu đang kích hoạt khi acedCommand() tạm dừng, menu có thể làm thỏa mãn yêu cầu nhập liệu đó. Nếu muốn đình chỉ thành phần trong menu, hãy thêm một dấu sổ ngược vào trong thành phần đó. Khi AutoCAD tìm thấy đầu vào phù hợp, cả hàm acedCommand() và item đều được tiếp tục lại.

Nếu hàm acedCommand() (hoặc acedCmd()) xác định PAUSE để lựa chọn SELECT và biến PICKFIRST đang bật, lệnh lựa chọn SELECT sẽ chọn ngay tập hợp PICKFIRST đang được chọn mà không tạm dừng cho phép người dùng nhập liệu.

Đưa một điểm pick vào trong lệnh AutoCAD

Một vài lệnh của AutoCAD (như TRIM, EXTEND và FILLET) yêu cầu người dùng xác định một điểm pick point cùng với thực thể. Để đưa một cặp dữ liệu thực thể và point với hàm acedCommand(), bạn phải xác định tên của thực thể trước và đóng cặp bằng mã kiểu kết quả RTLB và RTLE. Dưới đây là sơ đồ của định dạng này
RTLB, RTENAME, entity, RTPOINT, point, RTLE
Hoặc
RTLB, RTENAME, entity, RT3DPOINT, point, RTLE
Vị trí entity phải là tên thực thể hợp lệ có kiểu ads_name và point là một điểm kiểu ads_point. Bạn không thể đưa một chuỗi ký tự thay cho một điểm được.
Một chuỗi cấu trúc bộ đệm kết quả (result-buffer) có thể được tạo ra bằng cách gọi hàm acutBuildList() và sau đó khởi động dòng lệnh AutoCAD bằng cách đưa danh sách (dạng bộ đệm) vào hàm acedCmd().


Nguyên tắc sử dụng thông báo Notification

Khi sử dụng các thông báo (notification), hãy theo sát các nguyên tắc sau. Sử dụng thông báo mà vi phạm nguyên tắc này có thể dẫn đến kết quả khó lường trong ứng dụng của bạn.
  • Không nên phụ thuộc trình tự của thông báo được đưa ra.
Bạn có thể đếm trên hàm commandWillStart() được kích hoạt trước commandEnded(),beginInsert() được kích hoạt trước endInsert(). Phụ thuộc vào bất kỳ trình tự nào khác sẽ gây ra vấn đề cho ứng dụng nếu trình tự có thay đổi khi một thông báo mới được đưa ra hoặc thông báo cũ được sắp xếp lại.

  • Đừng phụ thuộc vào chuỗi các quả trình (gọi hàm) giữa các thông báo.
Nếu bạn không, ứng dụng của bạn có thể thất bại trong các phiên bản của tương lai.
Thay vì tin tưởng chuỗi, hãy dựa vào các thông báo để chỉ ra trạng thái của hệ thống. Ví dụ, khi nhận được thông báo erased(kTrue) trên đối tượng A, có nghĩa là đối tượng A bị xóa. Nếu bạn nhận được thông báo erased() trên A sau thông báo erased() trên B, có nghĩa là cả A và B đều đã bị xóa bỏ. Hệ thống sẽ không bảo đảm rằng B sẽ được xóa ngay sau A.
  • Không sử dụng bất kì hàm tương tác người dùng trong hàm trả về thông báo như acedCommand(), acedGetPoint(), acedGetKword(), hoặc bất kỳ hàm acedXXX() nào khác.
Giải thích tương tự áp dụng với các thông báo trên reactor CSDL, reactor chỉnh sửa và reactor giao dịch.

Sử dụng đối tượng phản ứng Object Reactor (phần 3)

Sự kiện tức thời và sự kiện hẹn trước (Commit-Time)

Với AcDbObjectReactor, sự kiện thông báo có thể diễn ra tức thời hoặc được trì hoãn đến thời điểm hẹn trước. Thời gian hẹn trước là thời gian đối tượng được đóng nếu bạn đang hoạt động trên mỗi đối đượng cơ sở, hoặc kết thúc giao dịch ngoài cùng khi bạn thực hiện kiểu giao dịch (transaction). Sự kiện sau sẽ gửi thông báo ngay lập tức:

cancelled()
    Thông báo được gửi đến AcDbObject khi gọi tới AcDbObject::cancelled().
openedForModify()
    Thông báo được gửi tới lần đầu tiên hàm chỉnh sửa đối tượng được gọi tới, trước khi trạng thái đối tượng bị thay đổi.
copied()
    Thông báo khi đối tượng được copy.
goodbye()
    Khi đối tượng bị xóa khỏi bộ nhớ.
Các thông báo tức thời được thực hiện cùng lúc với sự kiện tương ứng. Ví dụ, khi hàm assertWriteEnabled() được gọi lần đầu tiên trên đối tượng, thông báo openedForModify() ngay lập tức được gửi đi đến tất cả các reactor trên đối tượng đó.
Các sự kiện sau đây chỉ được gửi theo thời gian hẹn trước:
  • modified()
  • subObjModified()
  • erased()
  • modifyUndone()
  • modifiedXData()
  • unappended()
  • reappended()
  • graphicsModified()
Thông báo modified() của AcDbObjectReactor là một ví dụ của thông báo hẹn trước commit-time. Giả sử một đối tượng được mở và hàm chỉnh sửa được gọi đến. Hàm chỉnh sửa lại gọi hàm assertWriteEnabled() và tất cả các reactor sẽ nhận được phản ứng openedForModify(). Các hàm chỉnh sửa tiếp theo trên đối tượng sẽ không tạo ra bất kì thông báo nào khác. Khi đối tượng được đóng, thông báo modified() sẽ được gửi đi. Tuy nhiên, nếu trình mở chọn phương thức cancel() thay cho close() thì thông báo cancelled() sẽ được gửi thay thế.

Khi nhận được thông thông báo trì hoãn (deferred notification) như modified() trong thời gian hẹn trước (commit time), một trong số tham số là con trỏ tới đối tượng. Lúc này, đối tượng đang ở trạng thái chỉ đọc (read-only). Bạn không thể chỉnh sửa cho đến khi kết thúc quá trình hẹn trước được.
Cố gắng thay đổi đối tượng trước khi thời gian hẹn trước kết thúc sẽ khiến cho AutoCAD hủy bỏ với thông điệp lỗi eWasNotOpenForWrite hoặc eInProcessOfCommitting.
Bạn có thể sử dụng hàm sau để kiểm tra quá trình hẹn trước đã kết thúc hay chưa trước khi mở đối tượng để ghi:
AcDbObjectReactor::objectClosed(const AcDbObjectId objId);
AcTransactionReactor::transactionEnded(
        int& numTransactions,
        AcDbTransactionManager* transactionManagerPtr);

Thông báo objectClosed() được gửi khi đối tượng thực sự đóng lại và con trỏ không còn hợp lệ nữa. Bạn có thể mở lại đối tượng bằng ID đã đưa vào trong tham số và thao tác trên nó. Đừng nên tạo ra các vòng lặp thông báo vô hạn vào lúc này.

Trong thông báo transactionEnded(), bạn có thể sử dụng hàm numActiveTransactions() để hỏi trình quản lý giao dịch (transaction manager) xem có bao nhiêu giao dịch đang diễn ra. Nếu không còn giao dịch hiện thời, nghĩa là giao dịch đã kết thúc và tất cả các đối tượng trong giao dịch đã hoàn hành thời gian hẹn trước. 
Đôi khi bạn có thể cần biết khi nào giao dịch ngoài cùng kết thúc và quá trình hẹn trước bắt đầu. Muốn vậy, hãy sử dụng thông báo sau đây:
AcTransactionReactor::endCalledOnOutermostTransaction(
    int& numTransactions,
    AcDbTransactionManager* transactionManagerPtr);
Khi vòng giao dịch ngoài cùng kết thúc, quá trình hẹn trước bắt đầu và phương thức close() được gọi tới trên các đối tượng. Bạn phải nhận thông báo objectClosed() như là một phần của quá trình đóng. Tuy nhiên, tốt hơn hết là không hành động ngay lập tức. Thay vào đó, hãy đợi đến lúc toàn bộ giao dịch kết thúc trước ki tiến hành bất cứ quá trình nào trên các đối tượng này.

Sử dụng đối tượng phản ứng Object Reactor (phần 2)

Ví dụ: Xây dựng đối tượng phụ thuộc (Building in Object Dependencies )

Ví dụ dưới đây trình bầy cách để tạo ra các reactor để thiết lập các phụ thuộc (dependencies) giữa các đối tượng Cơ sở dữ liệu. Trong ví dụ này, khi bạn thay đổi một đoạn thẳng, đoạn thẳng kia cũng sẽ thay đổi theo.
class AsdkObjectToNotify : public AcDbObject
//
// AsdkObjectToNotify - customized AcDbObject for persistent
// reactor to notify.
//
{
public:
ACRX_DECLARE_MEMBERS(AsdkObjectToNotify);
AsdkObjectToNotify() {};
void eLinkage(AcDbObjectId i, double f=1.0)
{mId=i; mFactor=f; };
void modified(const AcDbObject*);
Acad::ErrorStatus dwgInFields(AcDbDwgFiler*);
Acad::ErrorStatus dwgOutFields(AcDbDwgFiler*) const;
Acad::ErrorStatus dxfInFields(AcDbDxfFiler*);
Acad::ErrorStatus dxfOutFields(AcDbDxfFiler*) const;
private:
AcDbObjectId mId;
double mFactor;
};
ACRX_DXF_DEFINE_MEMBERS(AsdkObjectToNotify, AcDbObject,
AcDb::kDHL_CURRENT, AcDb::kMReleaseCurrent,
0, ASDKOBJECTTONOTIFY, persreac);
Hàm sau đây sẽ được gọi bất cứ khi nào đoạn thẳng (line) mà nó theo dõi được chỉnh sửa. Khi hàm được gọi tới, nó mở đoạn thẳng kia trong cặp đoạn thẳng và thay đổi chiều dài của đoạn thẳng đó.
// This function is called every time the line it's
// "watching" is modified. When it's called, it opens the
// other line of the pair and changes that line's length to
// match the new length of the line that's just been
// modified.
//
void
AsdkObjectToNotify::modified(const AcDbObject* pObj)
{
AcDbLine *pLine = AcDbLine::cast(pObj);

if (!pLine) {
const char* cstr = pObj->isA()->name();
acutPrintf("This is a %s.\n", cstr);
acutPrintf("I only work with lines. Sorry.\n");
return;
}

acutPrintf("\nReactor attached to %lx calling %lx.\n",
pLine->objectId(), mId);

// This open will fail during notification caused by a
// reactor being added to the entity or when this
// notification is in reaction to a change due to the
// other line's reactor changing this line. This will
// properly prevent an infinite recursive loop
// between the two lines and their reactors.
//
AcDbLine *pLine2;

if (acdbOpenObject((AcDbObject*&)pLine2, mId,
AcDb::kForWrite) == Acad::eOk)
{
// Get length of line entity we're being notified
// has just been modified.
//
AcGePoint3d p = pLine->startPoint();
AcGePoint3d q = pLine->endPoint();
AcGeVector3d v = q-p;
double len = v.length();

// update other entity to match:
//
p = pLine2->startPoint();
q = pLine2->endPoint();
v = q-p;
v = len * mFactor * v.normal();
pLine2->setEndPoint(p+v);
pLine2->close();
}
}

// Files an object's information in.
//
Acad::ErrorStatus
AsdkObjectToNotify::dwgInFields(AcDbDwgFiler* filer)
{
assertWriteEnabled();
AcDbObject::dwgInFields(filer);
filer->readItem(&mFactor);
filer->readItem((AcDbSoftPointerId*) &mId);
return filer->filerStatus();
}

// Files an object's information out.
//
Acad::ErrorStatus
AsdkObjectToNotify::dwgOutFields(AcDbDwgFiler* filer) const
{
assertReadEnabled();
AcDbObject::dwgOutFields(filer);
filer->writeItem(mFactor);
filer->writeItem((AcDbSoftPointerId&)mId);
return filer->filerStatus();
}

// Files an object's information in from DXF and AutoLISP.
//
Acad::ErrorStatus
AsdkObjectToNotify::dxfInFields(AcDbDxfFiler* filer)
{
assertWriteEnabled();
Acad::ErrorStatus es;
if ((es = AcDbObject::dxfInFields(filer))
!= Acad::eOk)
{
return es;
}

// Check if we're at the right subclass data marker.
//
if(!filer->atSubclassData("AsdkObjectToNotify")) {
return Acad::eBadDxfSequence;
}

struct resbuf rbIn;

while (es == Acad::eOk) {
if ((es = filer->readItem(&rbIn)) == Acad::eOk) {
if (rbIn.restype == AcDb::kDxfReal) {
mFactor = rbIn.resval.rreal;
} else if (rbIn.restype
== AcDb::kDxfSoftPointerId)
{
// ObjectIds are filed in as ads_names.
//
acdbGetObjectId(mId, rbIn.resval.rlname);
} else { // invalid group
return(filer->pushBackItem());
}
}
}
return filer->filerStatus();
}

// Files an object's information out to DXF and AutoLISP.
//
Acad::ErrorStatus
AsdkObjectToNotify::dxfOutFields(AcDbDxfFiler* filer) const
{
assertReadEnabled();
AcDbObject::dxfOutFields(filer);
filer->writeItem(AcDb::kDxfSubclass,
"AsdkObjectToNotify");
filer->writeItem(AcDb::kDxfReal, mFactor);
filer->writeItem(AcDb::kDxfSoftPointerId, mId);
return filer->filerStatus();
}
Tạo ra hai đoạn thẳng và hai đối tượng AsdkObjectToNotify và ràng buộc (tie) chúng lại với nhau

// Creates two lines and two AsdkObjectToNotify objects and
// ties them all together.
//
void
assocLines()
{
AcDbDatabase *pDb =
acdbHostApplicationServices()->workingDatabase();
AcDbObjectId aId, bId;
AcDbLine *pLineA = new AcDbLine;
pLineA->setDatabaseDefaults(pDb);
pLineA->setStartPoint(AcGePoint3d(1, 1, 0));
pLineA->setEndPoint(AcGePoint3d(2, 1, 0));
addToModelSpace(aId, pLineA);

acutPrintf( "Line A is %lx from 1,1 to 2,1.\n",
pLineA->objectId());
AcDbLine *pLineB = new AcDbLine;
pLineB->setDatabaseDefaults(pDb);
pLineB->setStartPoint(AcGePoint3d(1, 2, 0));
pLineB->setEndPoint(AcGePoint3d(2, 2, 0));
addToModelSpace(bId, pLineB);
acutPrintf("Line B is %lx from 1,2 to 2,2.\n",
pLineB->objectId());
// Open the named object dictionary, and check if there is
// an entry with the key "ASDK_DICT". If not, create a
// dictionary and add it.
//
AcDbDictionary *pNamedObj;
AcDbDictionary *pNameList;
pDb->getNamedObjectsDictionary(pNamedObj,
AcDb::kForWrite);

if (pNamedObj->getAt("ASDK_DICT",
(AcDbObject*&)pNameList, AcDb::kForWrite)
== Acad::eKeyNotFound)
{
pNameList = new AcDbDictionary;
AcDbObjectId DictId;
pNamedObj->setAt("ASDK_DICT", pNameList, DictId);
}

pNamedObj->close();

// Create the AsdkObjectToNotify for line A.
//
AsdkObjectToNotify *pObj = new AsdkObjectToNotify();
pObj->eLinkage(bId);
AcDbObjectId objId;

if ((pNameList->getAt("object_to_notify_A", objId))
== Acad::eKeyNotFound)
{
pNameList->setAt("object_to_notify_A", pObj, objId);
pObj->close();
} else {
delete pObj;
acutPrintf("object_to_notify_A already exists\n");
}

// Set up persistent reactor link between line A
// and AsdkObjectToNotify.
//
pLineA->addPersistentReactor(objId);
pLineA->close();

// Create the AsdkObjectToNotify for line B.
//
pObj = new AsdkObjectToNotify();
pObj->eLinkage(aId);

if ((pNameList->getAt("object_to_notify_B", objId))
== Acad::eKeyNotFound)
{
pNameList->setAt("object_to_notify_B", pObj, objId);
pObj->close();
} else {
delete pObj;
acutPrintf("object_to_notify_B already exists\n");
}
pNameList->close();

// Set up persistent reactor link between line B
// and AsdkObjectToNotify.
//
pLineB->addPersistentReactor(objId);
pLineB->close();
}
Thêm thực thể vào không gian model, nhưng không đóng thực thể lại.

// Adds an entity to model space, but does not close
// the entity.
//
void
addToModelSpace(AcDbObjectId &objId, AcDbEntity* pEntity)
{
AcDbBlockTable *pBlockTable;
AcDbBlockTableRecord *pSpaceRecord;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pBlockTable, AcDb::kForRead);
pBlockTable->getAt(ACDB_MODEL_SPACE, pSpaceRecord,
AcDb::kForWrite);
pBlockTable->close();
pSpaceRecord->appendAcDbEntity(objId, pEntity);
pSpaceRecord->close();
return;
}
Các hàm khởi động và kết thúc quá trình làm việc.
// This is the initialization function called from acrxEntryPoint()
// during the kInitAppMsg case. This function is used to add
// commands to the command stack.
//
void
initApp()
{
acedRegCmds->addCommand("ASDK_ALINES", "ASDK_ALINES",
"ALINES", ACRX_CMD_MODAL, assocLines);
AsdkObjectToNotify::rxInit();
acrxBuildClassHierarchy();
}

// This is the clean-up function called from acrxEntryPoint() during
// the kUnloadAppMsg case. This function removes this application's
// command set from the command stack.
//
void
unloadApp()
{
acedRegCmds->removeGroup("ASDK_ALINES");

// Remove the AsdkObjectToNotify class from the ACRX
// runtime class hierarchy. If this is done while the
// database is still active, it should cause all objects
// of class AsdkObjectToNotify to be turned into proxies.
//
deleteAcRxClass(AsdkObjectToNotify::desc());
}

// ObjectARX entry point
//
extern "C" AcRx::AppRetCode
acrxEntryPoint(AcRx::AppMsgCode msg, void* appId)
{
switch (msg) {
case AcRx::kInitAppMsg:
acrxDynamicLinker->unlockApplication(appId);
acrxDynamicLinker->registerAppMDIAware(appId);
initApp();
break;
case AcRx::kUnloadAppMsg:
unloadApp();
}
return AcRx::kRetOK;
}

Sử dụng đối tượng phản ứng Object Reactor (phần 1)

Sử dụng đối tượng Object Reactor 

Cơ chế này cho phép bạn định nghĩa các phụ thuộc (dependencies) bên trong cơ sở dữ liệu cái mà được bảo toàn khi CSDL được ghi và được tái tạo lại mỗi khi đóng mở bản vẽ (reinstantiated).

Để tạo ra một đối tượng CSDL để phản hồi với một đối tượng khác:
  1. Dẫn xuất một lớp từ AcDbObject (hoặc bất kỳ lớp con nào khác).
  2. Thực thi các hàm thông báo.
  3. Khởi tạo một đối tượng của lớp mới.
  4. Thêm đối tượng vào CSDL, và đăng ký chủ sở hữu cho nó.
  5. Thêm nó vào đối tượng notifier với hàm AcDbObject::addPersistentReactor() .
Sử dụng macros ObjectARX khi dẫn xuất một lớp đối tượng reactor mới để tạo ra đối tượng mô tả lớp cho nó. (Nếu không sử dụng macro, lớp của bạn sẽ thừa kế bộ mô tả lớp của lớp cơ bản bên trên khi nó được ghi lại, và mã định danh của nó sẽ mất đi khi tệp tin được đọc trong nó.

Lấy mã định danh ID của reactor đối tượng (Object Reactor)

Tất cả các đối tượng CSDL đều mang một danh sách các reactor cho riêng mình. Một trong số đó là reactor nhất thời, và một số là liên tục. Reactor nhất thời là đại diện cho các lớp dẫn xuất từ AcDbObjectReactor, trong khi reactor liên tục lại là các mã định danh IDs của đối tượng thường trú CSDL.
Đoạn mã dưới đây thể hiện cách tìm trong danh sách reactor để lọc các reactor nhất thời hay liên tục. Việc xác nhận từng đầu mục trong danh sách reactor bằng hàm AcDbIsPersistentReactor là thực sự quan trọng. Nếu nó là một reactor nhất thời, bạn có thể sử dụng các hàm thích hợp để lấy mã ObjectId. Còn nếu không, bạn có thể đổi kiểu (cast) đầu mục thành AcDbObjectReactor.
AcDbVoidPtrArray *pReactors;
void *pSomething;
AcDbObjectReactor *pObjReactor;
AcDbObjectId persObjId;
AcDbObject *pPersReacObj;

pReactors = pEnt->reactors();

if (pReactors != NULL && pReactors->length() > 0) {
for (int i = 0; i < pReactors->length(); i++) {
pSomething = pReactors->at(i);

// Is it a persistent reactor?
//
if (acdbIsPersistentReactor(pSomething)) {
persObjId = acdbPersistentReactorObjectId(
pSomething);
acutPrintf("\n\nPersistent reactor found.");

// Echo the keyname to the user.
//
char *keyname = NULL;
getPersReactorKey(keyname, persObjId);
if (keyname) {
acutPrintf("\nThis is the reactor named %s",
keyname);
free (keyname);
}
// Open it up and see if it's one of ours. If it is,
// fire the custom notification.
//
if ((retStat =
acdbOpenAcDbObject(pPersReacObj,
persObjId, AcDb::kForNotify))
!= Acad::eOk)
{
acutPrintf("\nFailure for"
" openAcDbObject: retStat==%d\n",
retStat);
return;
}

AsdkPersReactor *pTmpPers;
if ((pTmpPers =
AsdkPersReactor::cast((AcRxObject*)
pPersReacObj)) != NULL)
{
pTmpPers->custom();
}
pPersReacObj->close();
} else {
// Or is it transient?
//
pObjReactor = (AcDbObjectReactor *)
(pReactors->at(i));
acutPrintf("\n\nTransient Reactor found");

// Report what kind we found.
//
if (pObjReactor->isKindOf(
AsdkSimpleObjReactor::desc()))
{
acutPrintf(" of type"
" AsdkSimpleObjReactor");
} else if (pObjReactor->isKindOf(
AcDbEntityReactor::desc()))
{
acutPrintf(" of type"
" AcDbEntityReactor");
} else if (pObjReactor->isKindOf(
AcDbObjectReactor::desc()))
{
acutPrintf(" of type"
" AcDbObjectReactor");
} else {
acutPrintf(" of unknown type.");
}
}
}
} else {
acutPrintf("\nThis entity has no reactors.\n");
}

Featured Post

Khóa học AutoLISP cơ bản | Tự học lập trình | AutoLISP that la don gian

Khóa học được cung cấp bởi  Thanh Trí AutoCAD       Thông tin thêm: 👉👉👉

Popular Posts