views:

446

answers:

3

I'm implementing an application which uses COM in AutoCAD's ObjectARX interface to automate drawing actions, such as open and save as.

According to the documentation, I should be able to call AcadDocument.SaveAs() and pass in a filename, a "save as type" and a security parameter. The documentation explicitly statses that if security is NULL, no security related operation is attempted. It doesn't, however, give any indication of the correct object type to pass as the "save as type" parameter.

I've tried calling SaveAs with a filename and null for the remaining arguments, but my application hangs on that method call and AutoCAD appears to crash - you can still use the ribbon but can't do anything with the toolbar and can't close AutoCAD.

I've got a feeling that it's my NULL parameters causing grief here, but the documentation is severely lacking in the COM/VBA department. In fact it says the AcadDocument class doesn't even have a SaveAs method, which it clearly does.

Has anyone here implemented the same thing? Any guidance?

The alternative is I use the SendCommand() method to send a _SAVEAS command, but my application is managing a batch of drawing and needs to know a) if the save fails, and b) when the save completes (which I'm doing by listening to the EndSave event.)

EDIT

Here's the code as requested - all it's doing is launching AutoCAD (or connecting to the running instance if it's already running), opening an existing drawing, then saving the document to a new location (C:\Scratch\Document01B.dwg.)

using (AutoCad cad = AutoCad.Instance)
{
    // Launch AutoCAD
    cad.Launch();

    // Open drawing
    cad.OpenDrawing(@"C:\Scratch\Drawing01.dwg");

    // Save it
    cad.SaveAs(@"C:\Scratch\Drawing01B.dwg");
}

Then in my AutoCad class (this._acadDocument is an instance of the AcadDocument class.)

public void Launch()
{
    this._acadApplication = null;
    const string ProgramId = "AutoCAD.Application.18";

    try
    {
        // Connect to a running instance
        this._acadApplication = (AcadApplication)Marshal.GetActiveObject(ProgramId);
    }
    catch (COMException)
    {
        /* No instance running, launch one */

        try
        {
            this._acadApplication = (AcadApplication)Activator.CreateInstance(
                Type.GetTypeFromProgID(ProgramId), 
                true);
        }
        catch (COMException exception)
        {
            // Failed - is AutoCAD installed?
            throw new AutoCadNotFoundException(exception);
        }
    }

    /* Listen for the events we need and make the application visible */
    this._acadApplication.BeginOpen += this.OnAcadBeginOpen;
    this._acadApplication.BeginSave += this.OnAcadBeginSave;
    this._acadApplication.EndOpen += this.OnAcadEndOpen;
    this._acadApplication.EndSave += this.OnAcadEndSave;

#if DEBUG
    this._acadApplication.Visible = true;
#else
    this._acadApplication.Visible = false;
#endif

    // Get the active document
    this._acadDocument = this._acadApplication.ActiveDocument;
}

public void OpenDrawing(string path)
{
    // Request AutoCAD to open the document
    this._acadApplication.Documents.Open(path, false, null);

    // Update our reference to the new document
    this._acadDocument = this._acadApplication.ActiveDocument;
}

public void SaveAs(string fullPath)
{
    this._acadDocument.SaveAs(fullPath, null, null);
}
A: 

Judging by the link to AutoDesk's forum on this topic, it sounds like as you need to close the object after saving...and remove the null's...If I were you, I'd wrap up the code into try/catch blocks to check and make sure there's no exception being thrown!

I have to question the usage of the using clause, as you're Launching another copy aren't you? i.e. within the OpenDrawing and Save functions you are using this._acadApplication or have I misunderstood?

using (AutoCad cad = AutoCad.Instance)
{
    try{
       // Launch AutoCAD
       cad.Launch();

       // Open drawing
       cad.OpenDrawing(@"C:\Scratch\Drawing01.dwg");

       // Save it
       cad.SaveAs(@"C:\Scratch\Drawing01B.dwg");
    }catch(COMException ex){
       // Handle the exception here
    }
}

public void Launch()
{
    this._acadApplication = null;
    const string ProgramId = "AutoCAD.Application.18";

    try
    {
        // Connect to a running instance
        this._acadApplication = (AcadApplication)Marshal.GetActiveObject(ProgramId);
    }
    catch (COMException)
    {
        /* No instance running, launch one */

        try
        {
            this._acadApplication = (AcadApplication)Activator.CreateInstance(
                Type.GetTypeFromProgID(ProgramId), 
                true);
        }
        catch (COMException exception)
        {
            // Failed - is AutoCAD installed?
            throw new AutoCadNotFoundException(exception);
        }
    }

    /* Listen for the events we need and make the application visible */
    this._acadApplication.BeginOpen += this.OnAcadBeginOpen;
    this._acadApplication.BeginSave += this.OnAcadBeginSave;
    this._acadApplication.EndOpen += this.OnAcadEndOpen;
    this._acadApplication.EndSave += this.OnAcadEndSave;

#if DEBUG
    this._acadApplication.Visible = true;
#else
    this._acadApplication.Visible = false;
#endif

    // Get the active document
    // this._acadDocument = this._acadApplication.ActiveDocument; 
    // Comment ^^^ out? as you're instantiating an ActiveDocument below when opening the drawing?
}

public void OpenDrawing(string path)
{
    try{
       // Request AutoCAD to open the document
       this._acadApplication.Documents.Open(path, false, null);

       // Update our reference to the new document
       this._acadDocument = this._acadApplication.ActiveDocument;
    }catch(COMException ex){
       // Handle the exception here
    }
}

public void SaveAs(string fullPath)
{
    try{
       this._acadDocument.SaveAs(fullPath, null, null);
    }catch(COMException ex){
       // Handle the exception here
    }finally{
       this._acadDocument.Close();
    }
}

Thought I'd include some links for your information.

Hope this helps, Best regards, Tom.

tommieb75
Thanks for trying Tom. No exception is being thrown - the app is hanging within AutoCAD's AcadDocument.SaveAs method - it even hangs AutoCAD not just my application. I cannot remove the NULLs because the SaveAs method requires 3 parameters, but there's no documentation telling me what to pass as the other 2 parameters. I also cannot close the drawing because I need to carry on working with it once its saved.
Andy Shellam
No I'm not launching another copy of AutoCAD, the code in the Launch() method uses the instance that's already running (which is then referenced by _acadApplication.) When you launch AutoCAD, it creates a blank document which you need to be able to send AutoCAD commands, hence the line you've commented out is required by my application. When Documents.Open() is called, it creates a new document and my app then references that one instead of the initial blank document.
Andy Shellam
@Andy: That's funny, in the links I provided, there is one parameter for the save?! Unless the COM has changed for the more recent release of AutoCAD - what version of AutoCAD actually are you using?
tommieb75
In the first link, they're using the CloseAndSave() and CloseAndDiscard() methods - I'm using SaveAs. I don't even have those methods. I do have a Close() method which takes bool (save changes) and string (where to save to) but I don't want to close the drawing, just save it. I'm on AutoCAD 2010 with ObjectARX.
Andy Shellam
A: 

I've managed to solve this in a non-optimal, very imperfect way so I'd still be interested to hear if anyone knows why the SaveAs method crashes AutoCAD and hangs my application.

Here's how I did it:

When opening a document or creating a new one, turn off the open/save dialog boxes:

this._acadDocument.SetVariable("FILEDIA", 0);

When saving a document, issue the _SAVEAS command passing in "2010" as the format and the filename (fullPath):

string commandString = string.Format(
    "(command \"_SAVEAS\" \"{0}\" \"{1}\") ",
    "2010",
    fullPath.Replace('\\', '/'));

this._acadDocument.SendCommand(commandString);

When exiting AutoCAD turn file dialog prompting back on (probably isn't necessary but just makes sure):

this._acadDocument.SetVariable("FILEDIA", 1);
Andy Shellam
@Andy: Delighted to hear you resolved it...bizarre way of doing it! What's even surprising, the docs failed to notify that!!!!?!!! WTF! :)
tommieb75
Yeah the documentation needs a massive overhaul. Luckily the native .NET API has brilliant documentation, but you can only use it within AutoCAD - not from an external application like I'm trying to do. Thanks anyway.
Andy Shellam
+1  A: 

From the Autodesk discussion groups, it looks like the second parameter is the type to save as, and may be required:

app = new AcadApplicationClass();
AcadDocument doc = app.ActiveDocument; doc.SaveAs("d:\Sam.dwg",AcSaveAsType.acR15_dwg,new Autodesk.AutoCAD.DatabaseServices.SecurityParameters());

Since you are in AutoCAD 2010, the type should be incremented to acR17_dwg or acR18_dwg.

Knyphe