I had a fun request, earlier in the week, that I thought worth turning into a couple of blog posts. The problem itself is quite specific, but some of the techniques shown – especially in the second of the two posts – seemed worth sharing.
The basic request was this: to implement a “quick SaveAs” command which will – the first time it’s run – ask for a file location and name and will then – each time it’s called subsequently – simply save the current drawing into the same folder with a filename based on the original with a incrementing suffix.
An example: the first time you run QSAVEAS you get presented with a SaveAs dialog and choose “c:\temp\Test.dwg”. Each time you call QSAVEAS afterwards a new drawing gets created in the temp folder: “Test 1.dwg”, “Test 2.dwg”, “Test 3.dwg”, etc.
This is likely to be a handy technique for people who want to take regular snapshots of their designs as they’re working.
There’s another dimension to this particular problem, however, which is why we’re looking at it over the course of two posts: imagine we’re in an environment where some kind of script or external data-file is being generated automatically for later recreation of the model. You can think of the mechanism as being a little like AutoCAD’s action recorder, although this is not about capturing user operations as much as it is about storing the information needed to recreate at a later point in time the model being worked upon.
We still want to save our DWG file – as this is a “snapshot” of the model that we can use in other systems taking AutoCAD drawings as inputs – but we also want to save our script/data-file, which is really the file that will be used to recreate the model. And at the same time we want to create an item on a special tool palette which will call a command to execute our script (or interpret our data-file). And this command tool needs to have the icon of the drawing we’ve just saved. Fun stuff! :-)
Anyway, before we get too carried away on the tool palette manipulation code – which will come in the next post – we’re going to look at our simple QSAVEAS command. I did want to set the stage appropriately, though, as the need to save the DWG and have its thumbnail preview available has driven some implementation decisions in today’s code.
One thing I should add, quickly: I’m not going to look at how we recreate our model from the script/data-file. That is really a much bigger problem and beyond the scope of these posts, which focus more on defining the QSAVEAS command and the steps needed to populate our tool palette. In the next post we will create a dummy script, but only to show how this can be picked up when our tool palette is used.
Here’s the C# code implementing our QSAVEAS command:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using System.IO;
namespace QuickSaveAs
{
public class Commands
{
// Set up static variable for the path to our folder
// of drawings, as well as the base filename and a
// counter to make the unique filename
static string _path = "",
_base = "";
static int _count = 0;
// Various filename and path-related constants
const string sfxSep = " ",
extSep = ".",
pthSep = "\\",
lspSep = "/",
dwgExt = ".dwg";
// Our QuickSaveAs command
[CommandMethod("QSAVEAS")]
public void QuickSaveAs()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
// If this is the first time run...
if (_path == "" || _base == "")
{
// Ask the user for a base file location
PromptSaveFileOptions opts =
new PromptSaveFileOptions(
"Select location to save first drawing file"
);
opts.Filter = "Drawing (*.dwg)|*.dwg";
PromptFileNameResult pr =
ed.GetFileNameForSave(opts);
// Delete the file, if it exists
// (may be a problem if the file is in use)
if (File.Exists(pr.StringResult))
{
try
{
File.Delete(pr.StringResult);
}
catch { }
}
if (pr.Status == PromptStatus.OK)
{
// If a file was selected, and it contains a path...
if (pr.StringResult.Contains(pthSep))
{
// Separate the path from the file name
int idx = pr.StringResult.LastIndexOf(pthSep);
_path =
pr.StringResult.Substring(0, idx);
string fullname =
pr.StringResult.Substring(idx + 1);
// If the path has an extension (this should always
// be the case), extract the base file name
if (fullname.Contains(extSep))
{
_base =
fullname.Substring(
0,
fullname.LastIndexOf(extSep)
);
}
}
}
}
// Assuming the path and name were set appropriately...
if (_path != "" && _base != "")
{
string name = _base;
// Add our suffix if not the first time run
if (_count > 0)
name += sfxSep + _count.ToString();
// Our drawing is located in the base path
string dwgPath = _path + pthSep + name + dwgExt;
// Now we want to save our drawing and use the image
// for our tool icon
// Using either COM or .NET doesn't generate a
// thumbnail in the resultant file (or its Database)
// .NET:
// db.SaveAs(dwgPath, false, DwgVersion.Current, null);
// COM:
// AcadDocument adoc = (AcadDocument)doc.AcadDocument;
// adoc.SaveAs(dwgPath, AcSaveAsType.acNative, null);
// So we'll send commands to the command-line
// We'll use LISP, to avoid having to set FILEDIA to 0
object ocmd = Application.GetSystemVariable("CMDECHO");
string dwgPath2 = dwgPath.Replace(pthSep, lspSep);
doc.SendStringToExecute(
"(setvar \"CMDECHO\" 0)" +
"(command \"_.SAVEAS\" \"\" \"" + dwgPath2 + "\")" +
"(setvar \"CMDECHO\" " + ocmd.ToString() + ")" +
"(princ) ",
false,
false,
false
);
// Print a confirmation message for the DWG save
// (which actually gets displayed before the queued
// string gets executed, but anyway)
ed.WriteMessage("\nSaved to: \"" + dwgPath + "\"");
_count++;
}
}
}
}
A few further comments on the implementation:
There are various ways to save a DWG file from .NET, but the requirement to have a thumbnail preview image created with the file (which is then accessible from its editor-resident Database) has limited our choice somewhat: neither Database.SaveAs() nor AcadDocument.SaveAs() (the .NET and COM methods) generate and save a thumbnail.
So we’re calling the SAVEAS command, which does generate the thumbnail. We could call this in a number of ways - COM’s AcadDocument.SendCommand() would call SAVEAS synchronously, for instance – but I’ve decided to use Document.SendStringToExecute() to fire off our SAVEAS and (eventually) launch a continuation function to update the tool palette.
We’re setting CMDECHO to keep the noise down on the command-line, but we’ve managed to avoid having to set FILEDIA to zero by wrapping the call in a LISP (command) call (which AutoCAD knows prefers command-line input).
Here’s what happens when we run the QSAVEAS command at regular intervals during an editing session:
Command: NETLOAD
Command: QSAVEAS
[Selected initial location via file selection dialog...]
Saved to: "C:\QSaveAs Test\Solid model.dwg"
[Various editing operations...]
Command: QSAVEAS
Saved to: "C:\QSaveAs Test\Solid model 1.dwg"
[Various editing operations...]
Command: QSAVEASSaved to: "C:\QSaveAs Test\Solid model 2.dwg"
[Various editing operations...]
Command: QSAVEASSaved to: "C:\QSaveAs Test\Solid model 3.dwg"
[Various editing operations...]
Command: QSAVEASSaved to: "C:\QSaveAs Test\Solid model 4.dwg"
[Various editing operations...]
Command: QSAVEASSaved to: "C:\QSaveAs Test\Solid model 5.dwg"
And here are the files, shown in Explorer:
Update:
This post shows a streamlined approach for this application for AutoCAD 2010 and above.