Some customers wanted to set sub entities of custom object to the specific layers. When the corresponding layers changed ( such as visibility, color etc), the entities will update accordingly.
The solution is very straightforward. worldDraw->subEntityTraits().setLayer allows us to specify the specific primitives of the custom object to the specific layer. This method sets the AcGiSubEntityTraits object to use layerId to specify the layer on which to draw graphics primitives until the next call to this function, or the end of the worldDraw() or viewportDraw() execution.
I did an experiment based on SDK sample PolySample. In asdkpolyobj project >> poly.cpp >> drawEdges method, add some lines as below.
static Acad::ErrorStatus drawEdges(const AsdkPoly* poly,
AcGiWorldDraw* worldDraw,
AcGiViewportDraw* vportDraw)
{
Acad::ErrorStatus es = Acad::eOk;
// Draw each edge of the polygon as a line. We could have drawn
// the whole polygon as a polyline, but we want to attach subEntity
// traits (e.g. which line it is) to each line which will be used
// for snap.
//
// Since we are drawing the polygon line by line, we also have the
// control over setting the linetype and color of each line (via
// subEntity traits), but we won't get that fancy.
//get the specific layers ID
AcDbObjectId testLayerId = AcDbObjectId::kNull;
AcDbObjectId zeroLayerId = AcDbObjectId::kNull;
AcDbLayerTable* lTable = NULL;
AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
Acad::ErrorStatus layer_table_es = pDb->getSymbolTable(lTable, AcDb::kForRead);
if (Acad::eOk == layer_table_es && lTable)
{
if (lTable->getAt(_T("MyTestLayer"), testLayerId) != Acad::eOk)
::acutPrintf(_T("ERROR Getting MyTestLayer\n"));
if (lTable->getAt(_T("0"), zeroLayerId) != Acad::eOk)
::acutPrintf(_T("ERROR Getting 0 Layer\n"));
lTable->close();
}
AcGePoint3dArray vertexArray;
if ((es = poly->getVertices3d(vertexArray)) != Acad::eOk) {
return es;
}
AcGePoint3d ptArray[2];
for (int i = 0; i < vertexArray.length() - 1; i++) {
if (worldDraw != NULL) {
if(i%3==0 ){
//if a specific edge. set it to the layer MyTestLayer
if(!testLayerId.isNull())
worldDraw->subEntityTraits().setLayer(testLayerId);
}
else {
//other edges. set them to the layer 0
if (!zeroLayerId.isNull())
worldDraw->subEntityTraits().setLayer(zeroLayerId);
}
worldDraw->subEntityTraits().setSelectionMarker(i + 1);
} else {
assert(Adesk::kFalse);
//vportDraw->subEntityTraits().setSelectionMarker(i + 1);
}
ptArray[0] = vertexArray[i];
ptArray[1] = vertexArray[i + 1];
if (worldDraw != NULL) {
worldDraw->geometry().polyline(2, ptArray);
} else {
assert(Adesk::kFalse);
//vportDraw->geometry().polyline3d(2, ptArray);
}
}
return es;
}
The snapshots are two tests. One toggle MyTestLayer off. The other change the layer's color.
There’s not that much work needed to port your apps to AutoCAD 2015(Longbow), unless you are using acedCommand or acedCmd.
The following is extracted from our DevDays PPT. If you feel tedious on my repeating the PPT, I’d recommend with the article written by our Blog Master Kean Walmsley : AutoCAD 2015: calling commands, or the video on the migration, or the demo samples codes which is availble at GitHub.
For this release, we’ve re-architected AutoCAD to completely strip out the use of Fibers and Fiber switching. Fibers are an old technology used in Windows that Microsoft stopped supporting several years ago. AutoCAD made heavy use of fibers and they are what allowed users to switch easily between drawings without interrupting commands. You’ve probably all seen this in action in old versions of AutoCAD – start a LINE command in one drawing, switch to another and draw a CIRCLE, then switch back to the first drawing and the LINE command is still active.
But using fibers was causing a lot of problems for us that we were spending more and more effort working around. For example, the .NET Framework has never supported Fiber Switching, and this is why when you switched documents in AutoCAD when debugging a .NET application, you’d often find that Visual Studio would fail to stop at a breakpoint– or it did break, but couldn’t display your code. That was Visual Studio getting confused by AutoCAD switching Fibers. You won’t experience that problem in Longbow now that we’ve removed Fibers. It also allows us to expose more of the ObjectARX SDK to .NET – the most exciting of which is that we now have a .NET version of acedCommand ((command) in LISP). That wasn’t possible before due to the problems caused by Fibers.
Migration in ObjectARX
That means ObjectARX has more significant migration requirements. There are two migration areas of concern: the MDI API and use of acedCommand/acedCmd. Then you’ll have to make changes. How big a change depends on how you’re using it.
If you’re passing a complete set of command tokens to the AutoCAD commandline, then all you have to do is add an ‘S’ to the end of acedCommand or acedCmd. S stands for subroutine. This is when you’re sending an entire command, and not pausing for user input.
If you’re sending a PAUSE, or if you’re sending an incomplete set of command tokens (for example starting a line command and leaving it for the user to finish) then you have a lot more work to do. In this situation, you’ll be using acedCommandC – the coroutine version of the command – and you’ll now have to pass a pointer to a callback function as a command parameter. It’s a lot more complicated – so much so that you might even consider migrating your code that needs this to .NET instead.
Here’s an example of the most basic acedCommand usage – its also the most popular use case – where we’re invoking a complete command in a single call to acedCommand. All you have to do here is change acedCommand to acedCommandS.
void foo(void)
{
acedCommandS(RTSTR,
_T("_Line"),
RTSTR,
_T("0,0"),
RTSTR,
_T("111,111"),
RTSTR, _T(""),
RTNONE);
acutPrintf(_T("\nFinished LINE command - \ control returned to addin\n"));
}
But a lot of people use acedCommand like this. Starting a command and then prompting for user input until the command ends. In this case we’re starting the LINE command and issuing PAUSEs to allow the user to keep drawing additional line segments. When we migrate this code for Longbow, we have to use acedCommandC, because acedCommandS doesn’t support partial command token stacks or PAUSEs.
void foo(void){
//Invoke partial command and leave command active.
acedCommandC(RTSTR,
_T("_Line"),
RTSTR,
_T("0,0"),
RTSTR,
_T("111,111"),
RTNONE); while(isLineActive())
acedCommandC(RTSTR,
PAUSE,
RTNONE); acutPrintf(_T("\nFinished LINE command - \
control returned to addin\n"));
}
The migrated code will look something like this. We’ve defined a callback function, which we pass to acedCommandC. Now when AutoCAD is ready to pass control from the user back to you, it will do so by calling the callback function you provided. In this example, the callback function keeps passing itself as a callback until the command ends. If the user cancels the command you invoked using acedCommand, then AutoCAD will also cancel your command that called it. Otherwise, control is handed back to your code when the command invoked by acedCommand ends.
void foo(void){
//Invoke partial command and leave command active.
acedCommandC(&myCallbackFn,
NULL, RTSTR,
_T("_Line"),
RTSTR,
_T("0,0"),
RTSTR,
_T("111,111"),
RTNONE);
//Control is passed to myCallbackFn1 from AutoCAD
}
//Your callback function int myCallbackFn(void * pData){
int nReturn = RTNONE;
//Keep command active until user ends it.
if (isLineActive())
nReturn = acedCommandC(&myCallbackFn,
NULL, RTSTR,
PAUSE, RTNONE);
else
{
//This is never reached if the user cancels the command –
// that cancels your command as well.It is reached if command is ended
//(e.g. by hitting ENTER)
acutPrintf(_T("\nFinished LINE command - \
control returned to addin\n"));
}
return nReturn;
}
Migration in .NET
I would say the most exciting new API in Longbow is the introduction of .NET versions of acedCommand ((command) in LISP). This hasn’t been possible before because of AutoCAD’s use of Fibers – and because the .NET Framework doesn’t support Fibers. So these new APIs – synchronous and asynchronous versions of acedCommand – are now possible for the first time.
acedCommand is probably the most powerful API in ObjectARX. It allows you to embed entire AutoCAD commands within your own commands; and even to invoke partial commands, and then programmatically respond to how the user interacts with those commands. This saves ObjectARX and LISP developers a huge amount of work where otherwise they’d have to reproduce all the native command behavior in their own code – and now .NET developers get the same benefit.
The synchronous version of Command is Editor.Command, the equivalent of the subroutine version of acedCommand. This is how you invoke a complete command from your code. As you can see, the code is very similar to the ObjectARX version – you pass a full set of command tokens to the AutoCAD commandline (the editor), AutoCAD completes the command and immediately returns control back to your code.
//simple use of Editor.Command
//most popular case - invoke a whole command with no user interaction
[CommandMethod(“MyCommand")]
publicstaticvoid MyMethod()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
ed.Command(newObject[] {
"_.LINE", "0,0,0", "10,10,0", "" });
}
There is also the asynchronous version of Command Editor.CommandAsync. This is the equivalent of the coroutine version of acedCommand. You’ll recall that acedCommandC required you to pass in a reference to a callback function. The .NET version is a little simpler. Because we’re using .NET Framework 4.5, we can make use of the async and await keywords to greatly simplify our asyncronous code. We mark our .NET commandmethod with the async keyword, and we can then use the await keyword inside that method. The await keyword tells AutoCAD where in your code to return control to after it has finished gathering user input. In this example, we’re simply starting the circle comamnd, using a PAUSE to let the user specify the circle center, and then specifying the circle radius in our code.
//Invoke a partial command with user interaction
[CommandMethod("DotNetType2")]
publicstaticasyncvoid DotNetType2()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
//create a circle of radius 2.0 at chosen location.
Question I'm trying to change DIMPOST to \X in a lisp routine. The program will not accept \X in a lsp program. I'm want to change Dimstyle using the set up; Method set to NONE for both primary(Metric) and alternate units(English) and alternate unit placement under the primary units. I can do this manually but not in the lisp.
Solution With these Lisp statements, the dimension formatting were as requested by the developer :
<<<
; To reset the alternative dimension prefix and suffix
(command-s "DIMAPOST" ".")
; To reset the primary dimension prefix and suffix
(command-s "DIMPOST" ".")
; To set the orientation of the alternative dimension over the primary dimension
AutoCAD API provides AcEdInputPointManager.Inputpointmonitor which can monitor any input of the user, including mouse. API also allows you to monitor Windows message. Sometimes you may just need to get the current mouse position without any event. The following is code demo. Actually, it just get the current cursor position and convert it to AutoCAD coordinate. It also takes UCS into consideration.
staticvoid getMousePosition(void)
{
//get cursor position by Windows API
POINT CursorPos;
GetCursorPos(&CursorPos);
acedGetAcadDwgView()->ScreenToClient(&CursorPos);
//Returns the viewport number based on
// Windows client coordinates.
int vpNum = acedGetWinNum(CursorPos.x, CursorPos.y);
//Converts coordinates from AutoCAD // drawing window
Our colleauge Kean has produced a blog on zooming, panning, orbiting the current view in .NET. I am writing the code in ObjectARX. The basic workflow for zooming, panning, orbiting is similar:
1. get he current view record
2. adjust its parameters. e.g.
zooming: adjust height and width with a factor
panning: adjust the view center
orbiting: adjust the view direction along an axis.
3. update the current view with the updated view record.
Issue How can you access the data in a database rows using Visual LISP functions ?
Solution Although there are no direct Visual LISP functions to read databases, you can use Microsoft's ActiveX ADO technology from AutoLISP to do this.
The following code illustrates how to read the Customer table from the NorthWind sample database in the Office products using either the Microsoft Access drivers or ODBC.
;; You'll Need To Change the Path to the Northwind.mdb file ;; L:\Program Files\Microsoft Office\Office\Samples ;;; Set MDB_File to a locatable Access Database file ->
(if(setq MDB_File (findfile "c:\\Northwind.mdb")) (setq MDB_File "c:\\Northwind") (progn (princ"\nNorthWind Database was not found, Exiting.") (exit) ) )
;;; Setup a SQL statement -> (setq SQLStatement "SELECT * FROM CUSTOMERS")
;;; Normal path to the ADO DLL, change as needed: (setq ADO_DLLPath "c:\\program files\\common files\\system\\ado\\")
;;; Proceed if Type Library has been loaded. (if adom-Append (progn ;;; Create A Connection Object (setq ConnectionObject (vlax-create-object"ADODB.Connection"))
;;; Use the direct Microsoft Access Drivers Creates a LDB Locking file here after this executes ;;; (vlax-invoke-method ;;; ConnectionObject ;;; "Open" ;;; (strcat"Driver={Microsoft Access Driver (*.mdb)};DBQ=" ;;; MDB_File ;;; ) ;;; "admin" ;;; "" ;;; adok-adConnectUnspecified ;;; )
;;; Or Use a ODBC Connection, but set it up first -> (vlax-invoke-method ConnectionObject "Open" "DSN=myMDB;" "admin" "" adok-adConnectUnspecified )
;;; The following code will not work, do not use it. ;(setq RecordSetObject ;;; The following fails with a "Type mismatch" error ; (vlax-invoke-method ; ConnectionObject "Execute" SQLStatement 'RecordsAffected ; adok-adCmdText ; ) ; )
;;; Create a Command Object and Connect it to the Connection Object (setq aCommandObject (vlax-create-object"ADODB.Command")) (vlax-put-property aCommandObject 'ActiveConnection ConnectionObject)
(setq reccnt (vlax-get-property fieldsObj 'Count) i 0) (princ(strcat"\nRecord "(itoa recno)":\n")) (while(< i reccnt) (setq afield (vlax-get-property fieldsObj 'Item i)) (princ(setq fvalue (vlax-variant-value(vlax-get-property afield 'Value)))) (princ"\t") (setq i (1+ i)) ) (princ"\n") (setq recno (1+ recno)) (vlax-invoke-method RecordSetObject 'MoveNext) ) ) (princ(strcat"\nRecord Set NOT Available, Record Set State = "(itoa rsState))) )
;;; Close only works if it's Opened !, this can usually be tested by checking if a LDB file exists. ;;; Check to see that the Connection Object is open here -> (setq coState (vlax-get-property ConnectionObject 'State)) (if(and (findfile (strcat MDB_File ".ldb"))(= coState adok-adStateOpen)) (vlax-invoke-method ConnectionObject "Close") (princ"\nConnection Object is not Open, please delete the LDB file if needed.") ) (Clean_Up) ) ;;; Exit if Type Library has NOT been loaded. (progn (alert(strcat"Exiting - Type Library was Not Loaded: \"" ADO_DLLPath "msado15.dll\"")) (exit) ) ) )
Issue How can I discover what tables are available AutoLISP?
Solution
There are two ASILISP functions that help here. Both return cursors to system tables so you can step through the records to find what you need. For example, this code will return all the available providers:
Issue Can I gain access to the "Open File Dialog" (with preview) found only in AutoCAD with VBA?
Solution You can do this through the communication interface with AutoLISP and AutoCAD. The AutoLISP function, getfiled, behaves as the AutoCAD "Open File Dialog" and allows .DWG files to be previewed.
VBA:
Public Sub OpenDialog() Dim fileName As String 'Using the SendCommand method, send getfiled AutoLISP expressions to the AutoCAD command line. 'Set the return value to a user-defined system variable USERS1. ThisDrawing.SendCommand "(setvar " & """users1""" & "(getfiled " & """Select a DWG File""" & """c:/program files/acad2012/""" & """dwg""" & "8)) " 'Use the GetVariable method to retrieve this system variable to store the selected file name fileName = ThisDrawing.GetVariable("users1") MsgBox "You have selected " & fileName & "!!!", , "File Message" End Sub
VB.NET
PublicSub OpenDialog(AcadApp AsAcadApplication)
Dim ThisDrawing AsAcadDocument
ThisDrawing = AcadApp.ActiveDocument
Dim fileName AsString
'Using the SendCommand method, send getfiled ‘AutoLISP expressions to the AutoCAD command
Issue How do I delete a database link from an entity using the CAO API and Visual LISP?
Solution The following is a short Visual LISP example that prompts the user to select an object. It then deletes any links that are associated with that object.
;;; This function deletes a link from a selected object
(defun c:DLink () (vl-load-com)
;;; Get the object and get the object ID (setq ent1 (car(entsel"\nSelect entity to erase links"))) (setq Obj (vlax-ename->vla-object ent1)) (setq ob_ID (vla-get-objectid Obj))
;;; Instantiate the DBConnect object (setq dbConnect (vlax-create-object"CAO.DbConnect.16"))
;;; Get the linkTemplates (setq LTs (vlax-invoke-method dbConnect "GetLinkTemplates"))
;;; Get a linkTemplate named "EmployeeLink1", This linkTemplate ;;; can be created from the Employee table in the DB sample that ships with ;;; AutoCAD (setq LT (vlax-invoke-method LTs "Item""EmployeeLink1"))
;;; Get the links using The linkTemplate (setq linkSel (vlax-invoke-method dbConnect "GetLinks" LT nil nil nil))
;;; Iterate through the links and delete the link ;;; if it has the same ObjectID as the link (vlax-for thisLink linkSel
Recent Comments