« August 2007 | Main | October 2007 »
Driving a multi-sheet AutoCAD plot using .NET
Somewhat symmetrically I’m posting this from Chicago airport, once again, but thankfully I’m now on my way home. It was a busy week of meetings, but I did get the chance to put together some code that extended the last post into the realm of multi-sheet plot jobs.
The following code took some work, but I finally managed to iron out the obvious wrinkles and put together an approach to plot multiple sheets into a single document. The standard DWF6 driver doesn’t appear to support multiple sheet jobs (directly, at least), so I chose to use the DWFx driver that I probably downloaded and installed from here.
I haven’t “diffed” and colour-coded the changed lines with the previous post, as there ended up being quite a lot of swapping around etc., but you should be able to perform that comparison yourself, if you so wish.
Here’s the C# code:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.PlottingServices;
namespace PlottingApplication
{
public class PlottingCommands
{
[CommandMethod("mplot")]
static public void MultiSheetPlot()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
PlotInfo pi = new PlotInfo();
PlotInfoValidator piv =
new PlotInfoValidator();
piv.MediaMatchingPolicy =
MatchingPolicy.MatchEnabled;
// A PlotEngine does the actual plotting
// (can also create one for Preview)
if (PlotFactory.ProcessPlotState ==
ProcessPlotState.NotPlotting)
{
PlotEngine pe =
PlotFactory.CreatePublishEngine();
using (pe)
{
// Create a Progress Dialog to provide info
// and allow thej user to cancel
PlotProgressDialog ppd =
new PlotProgressDialog(false, 1, true);
using (ppd)
{
ObjectIdCollection layoutsToPlot =
new ObjectIdCollection();
foreach (ObjectId btrId in bt)
{
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
btrId,
OpenMode.ForRead
);
if (btr.IsLayout &&
btr.Name.ToUpper() !=
BlockTableRecord.ModelSpace.ToUpper())
{
layoutsToPlot.Add(btrId);
}
}
int numSheet = 1;
foreach (ObjectId btrId in layoutsToPlot)
{
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
btrId,
OpenMode.ForRead
);
Layout lo =
(Layout)tr.GetObject(
btr.LayoutId,
OpenMode.ForRead
);
// We need a PlotSettings object
// based on the layout settings
// which we then customize
PlotSettings ps =
new PlotSettings(lo.ModelType);
ps.CopyFrom(lo);
// The PlotSettingsValidator helps
// create a valid PlotSettings object
PlotSettingsValidator psv =
PlotSettingsValidator.Current;
// We'll plot the extents, centered and
// scaled to fit
psv.SetPlotType(
ps,
Autodesk.AutoCAD.DatabaseServices.PlotType.Extents
);
psv.SetUseStandardScale(ps, true);
psv.SetStdScaleType(ps, StdScaleType.ScaleToFit);
psv.SetPlotCentered(ps, true);
// We'll use the standard DWFx PC3, as
// this supports multiple sheets
psv.SetPlotConfigurationName(
ps,
"DWFx ePlot (XPS Compatible).pc3",
"ANSI_A_(8.50_x_11.00_Inches)"
);
// We need a PlotInfo object
// linked to the layout
pi.Layout = btr.LayoutId;
// Make the layout we're plotting current
LayoutManager.Current.CurrentLayout =
lo.LayoutName;
// We need to link the PlotInfo to the
// PlotSettings and then validate it
pi.OverrideSettings = ps;
piv.Validate(pi);
if (numSheet == 1)
{
ppd.set_PlotMsgString(
PlotMessageIndex.DialogTitle,
"Custom Plot Progress"
);
ppd.set_PlotMsgString(
PlotMessageIndex.CancelJobButtonMessage,
"Cancel Job"
);
ppd.set_PlotMsgString(
PlotMessageIndex.CancelSheetButtonMessage,
"Cancel Sheet"
);
ppd.set_PlotMsgString(
PlotMessageIndex.SheetSetProgressCaption,
"Sheet Set Progress"
);
ppd.set_PlotMsgString(
PlotMessageIndex.SheetProgressCaption,
"Sheet Progress"
);
ppd.LowerPlotProgressRange = 0;
ppd.UpperPlotProgressRange = 100;
ppd.PlotProgressPos = 0;
// Let's start the plot, at last
ppd.OnBeginPlot();
ppd.IsVisible = true;
pe.BeginPlot(ppd, null);
// We'll be plotting a single document
pe.BeginDocument(
pi,
doc.Name,
null,
1,
true, // Let's plot to file
"c:\\test-multi-sheet"
);
}
// Which may contain multiple sheets
ppd.StatusMsgString =
"Plotting " +
doc.Name.Substring(
doc.Name.LastIndexOf("\\") + 1
) +
" - sheet " + numSheet.ToString() +
" of " + layoutsToPlot.Count.ToString();
ppd.OnBeginSheet();
ppd.LowerSheetProgressRange = 0;
ppd.UpperSheetProgressRange = 100;
ppd.SheetProgressPos = 0;
PlotPageInfo ppi = new PlotPageInfo();
pe.BeginPage(
ppi,
pi,
(numSheet == layoutsToPlot.Count),
null
);
pe.BeginGenerateGraphics(null);
ppd.SheetProgressPos = 50;
pe.EndGenerateGraphics(null);
// Finish the sheet
pe.EndPage(null);
ppd.SheetProgressPos = 100;
ppd.OnEndSheet();
numSheet++;
}
// Finish the document
pe.EndDocument(null);
// And finish the plot
ppd.PlotProgressPos = 100;
ppd.OnEndPlot();
pe.EndPlot(null);
}
}
}
else
{
ed.WriteMessage(
"\nAnother plot is in progress."
);
}
}
}
}
}
The output of the MPLOT command will be created in “c:\test-multi-sheet.dwfx”, which can then be viewed using Autodesk Design Review 2008 or the XPS viewer that ships with Windows Vista or from here for Windows XP.
Update
I spent some more time looking at this code and noticed a minor issue... We need to tell the plot dialog that we're working with multiple sheets in its constructor. So we first need to count the sheets and then create the dialog. Here's the modified section of code:
PlotEngine pe =
PlotFactory.CreatePublishEngine();
using (pe)
{
// Collect all the paperspace layouts
// for plotting
ObjectIdCollection layoutsToPlot =
new ObjectIdCollection();
foreach (ObjectId btrId in bt)
{
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
btrId,
OpenMode.ForRead
);
if (btr.IsLayout &&
btr.Name.ToUpper() !=
BlockTableRecord.ModelSpace.ToUpper())
{
layoutsToPlot.Add(btrId);
}
}
// Create a Progress Dialog to provide info
// and allow thej user to cancel
PlotProgressDialog ppd =
new PlotProgressDialog(
false,
layoutsToPlot.Count,
true
);
using (ppd)
{
This now leads to the plot progress dialog showing multiple progress bars:
September 29, 2007 in AutoCAD, AutoCAD .NET, Plotting | Permalink | Comments (7) | TrackBack
Driving a basic AutoCAD plot using .NET
I just missed my connecting flight in Chicago, so have 3 hours to pass until the next, and decided to post some code I finally got around to writing on the plane from Zurich.
I've had a few requests for code showing how to plot using the .NET API in AutoCAD. There's an existing ObjectARX (C++) sample on the SDK, under samples/editor/AsdkPlotAPI, but there isn't a publicly posted .NET version right now.
Here's the C# code I put together. Please bear in mind that it was written during less than ideal coding conditions, and I haven't spent a substantial amount of time going through it... it seems to work fine for me, but do post a comment if you have trouble with it.
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.PlottingServices;
namespace PlottingApplication
{
public class PlottingCommands
{
[CommandMethod("simplot")]
static public void SimplePlot()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
// We'll be plotting the current layout
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId,
OpenMode.ForRead
);
Layout lo =
(Layout)tr.GetObject(
btr.LayoutId,
OpenMode.ForRead
);
// We need a PlotInfo object
// linked to the layout
PlotInfo pi = new PlotInfo();
pi.Layout = btr.LayoutId;
// We need a PlotSettings object
// based on the layout settings
// which we then customize
PlotSettings ps =
new PlotSettings(lo.ModelType);
ps.CopyFrom(lo);
// The PlotSettingsValidator helps
// create a valid PlotSettings object
PlotSettingsValidator psv =
PlotSettingsValidator.Current;
// We'll plot the extents, centered and
// scaled to fit
psv.SetPlotType(
ps,
Autodesk.AutoCAD.DatabaseServices.PlotType.Extents
);
psv.SetUseStandardScale(ps, true);
psv.SetStdScaleType(ps, StdScaleType.ScaleToFit);
psv.SetPlotCentered(ps, true);
// We'll use the standard DWF PC3, as
// for today we're just plotting to file
psv.SetPlotConfigurationName(
ps,
"DWF6 ePlot.pc3",
"ANSI_A_(8.50_x_11.00_Inches)"
);
// We need to link the PlotInfo to the
// PlotSettings and then validate it
pi.OverrideSettings = ps;
PlotInfoValidator piv =
new PlotInfoValidator();
piv.MediaMatchingPolicy =
MatchingPolicy.MatchEnabled;
piv.Validate(pi);
// A PlotEngine does the actual plotting
// (can also create one for Preview)
if (PlotFactory.ProcessPlotState ==
ProcessPlotState.NotPlotting)
{
PlotEngine pe =
PlotFactory.CreatePublishEngine();
using (pe)
{
// Create a Progress Dialog to provide info
// and allow thej user to cancel
PlotProgressDialog ppd =
new PlotProgressDialog(false, 1, true);
using (ppd)
{
ppd.set_PlotMsgString(
PlotMessageIndex.DialogTitle,
"Custom Plot Progress"
);
ppd.set_PlotMsgString(
PlotMessageIndex.CancelJobButtonMessage,
"Cancel Job"
);
ppd.set_PlotMsgString(
PlotMessageIndex.CancelSheetButtonMessage,
"Cancel Sheet"
);
ppd.set_PlotMsgString(
PlotMessageIndex.SheetSetProgressCaption,
"Sheet Set Progress"
);
ppd.set_PlotMsgString(
PlotMessageIndex.SheetProgressCaption,
"Sheet Progress"
);
ppd.LowerPlotProgressRange = 0;
ppd.UpperPlotProgressRange = 100;
ppd.PlotProgressPos = 0;
// Let's start the plot, at last
ppd.OnBeginPlot();
ppd.IsVisible = true;
pe.BeginPlot(ppd, null);
// We'll be plotting a single document
pe.BeginDocument(
pi,
doc.Name,
null,
1,
true, // Let's plot to file
"c:\\test-output"
);
// Which contains a single sheet
ppd.OnBeginSheet();
ppd.LowerSheetProgressRange = 0;
ppd.UpperSheetProgressRange = 100;
ppd.SheetProgressPos = 0;
PlotPageInfo ppi = new PlotPageInfo();
pe.BeginPage(
ppi,
pi,
true,
null
);
pe.BeginGenerateGraphics(null);
pe.EndGenerateGraphics(null);
// Finish the sheet
pe.EndPage(null);
ppd.SheetProgressPos = 100;
ppd.OnEndSheet();
// Finish the document
pe.EndDocument(null);
// And finish the plot
ppd.PlotProgressPos = 100;
ppd.OnEndPlot();
pe.EndPlot(null);
}
}
}
else
{
ed.WriteMessage(
"\nAnother plot is in progress."
);
}
}
}
}
}
A few comments on the code: I chose to plot to a file using the standard DWF plot driver/PC3 file, mainly as it avoids having to second-guess what printers/plotters everyone uses. :-) The SDK sample populates comboboxes in a dialog to allow selection of the device and the media size, but I've just gone with a choice that should work on all systems.
Here's what you should see when you launch the SIMPLOT command:
The output file should be created in "c:\test-output.dwf".
I added a simple check to fail gracefully when another plot is in progress... as this code will drive either a foreground or a background plot (depending on the value of the BACKGROUNDPLOT system variable), there's clear scope for failure if the code doesn't check via PlotFactory.ProcessPlotState (or handle the exception if another plot is already happening when you call PlotFactory.CreatePublishEngine()).
Now that I'm online I've found there's quite a similar code sample to the ADN site (also converted from the original SDK sample, I suspect):
How to plot using AutoCAD's .NET Managed API?
September 25, 2007 in AutoCAD, AutoCAD .NET, Plotting | Permalink | Comments (2) | TrackBack
Creating a multileader in AutoCAD using a jig from .NET
I'm now back from a fantastic break in Italy and am trying hard to catch back up. Next week I'm off again to San Diego (work, this time), which may cause further interruptions in blog postings.
This question came through from Genésio from Brazil:
I wish jig a leader with an bubble in the and of the leader, at the same time. Can you help me. Perhaps post the solution in your blog (through the interface).
It took me a while - frustratingly long, in fact, and probably this is not exactly what Genésio is after - but here's what I managed to come up with. The "bubble" is framed MText, but it should be modifiable to use a classic bubble block, instead. I drew heavily on this previous post for the jig code.
The positioning of the text took some work, but I'm reasonably happy with the results. If anyone has tweaks to suggest, please post a comment.
Here's the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
namespace DimensionLibrary
{
public class DimensionCmds
{
class MLeaderJig : EntityJig
{
Point3dCollection m_pts;
Point3d m_tempPoint;
string m_contents;
int m_leaderIndex;
int m_leaderLineIndex;
public MLeaderJig(string contents)
: base(new MLeader())
{
// Store the string passed in
m_contents = contents;
// Create a point collection to store our vertices
m_pts = new Point3dCollection();
// Create mleader and set defaults
MLeader ml = Entity as MLeader;
ml.SetDatabaseDefaults();
// Set up the MText contents
ml.ContentType = ContentType.MTextContent;
MText mt = new MText();
mt.SetDatabaseDefaults();
mt.Contents = m_contents;
ml.MText = mt;
ml.TextAlignmentType =
TextAlignmentType.LeftAlignment;
ml.TextAttachmentType =
TextAttachmentType.AttachmentMiddle;
// Set the frame and landing properties
ml.EnableDogleg = true;
ml.EnableFrameText = true;
ml.EnableLanding = true;
// Reduce the standard landing gap
ml.LandingGap = 0.05;
// Add a leader, but not a leader line (for now)
m_leaderIndex = ml.AddLeader();
m_leaderLineIndex = -1;
}
protected override SamplerStatus Sampler(
JigPrompts prompts
)
{
JigPromptPointOptions opts =
new JigPromptPointOptions();
// Not all options accept null response
opts.UserInputControls =
(UserInputControls.Accept3dCoordinates |
UserInputControls.NoNegativeResponseAccepted
);
// Get the first point
if (m_pts.Count == 0)
{
opts.UserInputControls |=
UserInputControls.NullResponseAccepted;
opts.Message =
"\nStart point of multileader: ";
opts.UseBasePoint = false;
}
// And the second
else if (m_pts.Count == 1)
{
opts.BasePoint = m_pts[m_pts.Count - 1];
opts.UseBasePoint = true;
opts.Message =
"\nSpecify multileader vertex: ";
}
// And subsequent points
else if (m_pts.Count > 1)
{
opts.UserInputControls |=
UserInputControls.NullResponseAccepted;
opts.BasePoint = m_pts[m_pts.Count - 1];
opts.UseBasePoint = true;
opts.SetMessageAndKeywords(
"\nSpecify multileader vertex or [End]: ",
"End"
);
}
else // Should never happen
return SamplerStatus.Cancel;
PromptPointResult res =
prompts.AcquirePoint(opts);
if (res.Status == PromptStatus.Keyword)
{
if (res.StringResult == "End")
{
return SamplerStatus.Cancel;
}
}
if (m_tempPoint == res.Value)
{
return SamplerStatus.NoChange;
}
else if (res.Status == PromptStatus.OK)
{
m_tempPoint = res.Value;
return SamplerStatus.OK;
}
return SamplerStatus.Cancel;
}
protected override bool Update()
{
try
{
if (m_pts.Count > 0)
{
// Set the last vertex to the new value
MLeader ml = Entity as MLeader;
ml.SetLastVertex(
m_leaderLineIndex,
m_tempPoint
);
// Adjust the text location
Vector3d dogvec =
ml.GetDogleg(m_leaderIndex);
double doglen =
ml.DoglegLength;
double landgap =
ml.LandingGap;
ml.TextLocation =
m_tempPoint +
((doglen + landgap) * dogvec);
}
}
catch (System.Exception ex)
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
doc.Editor.WriteMessage(
"\nException: " + ex.Message
);
return false;
}
return true;
}
public void AddVertex()
{
MLeader ml = Entity as MLeader;
// For the first point...
if (m_pts.Count == 0)
{
// Add a leader line
m_leaderLineIndex =
ml.AddLeaderLine(m_leaderIndex);
// And a start vertex
ml.AddFirstVertex(
m_leaderLineIndex,
m_tempPoint
);
// Add a second vertex that will be set
// within the jig
ml.AddLastVertex(
m_leaderLineIndex,
new Point3d(0, 0, 0)
);
}
else
{
// For subsequent points,
// just add a vertex
ml.AddLastVertex(
m_leaderLineIndex,
m_tempPoint
);
}
// Reset the attachment point, otherwise
// it seems to get forgotten
ml.TextAttachmentType =
TextAttachmentType.AttachmentMiddle;
// Add the latest point to our history
m_pts.Add(m_tempPoint);
}
public void RemoveLastVertex()
{
// We don't need to actually remove
// the vertex, just reset it
MLeader ml = Entity as MLeader;
if (m_pts.Count >= 1)
{
Vector3d dogvec =
ml.GetDogleg(m_leaderIndex);
double doglen =
ml.DoglegLength;
double landgap =
ml.LandingGap;
ml.TextLocation =
m_pts[m_pts.Count - 1] +
((doglen + landgap) * dogvec);
}
}
public Entity GetEntity()
{
return Entity;
}
}
[CommandMethod("MYML")]
public void MyMLeaderJig()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
// Get the text outside of the jig
PromptStringOptions pso =
new PromptStringOptions(
"\nEnter text: "
);
pso.AllowSpaces = true;
PromptResult pr =
ed.GetString(pso);
if (pr.Status == PromptStatus.OK)
{
// Create MleaderJig
MLeaderJig jig =
new MLeaderJig(pr.StringResult);
// Loop to set vertices
bool bSuccess = true, bComplete = false;
while (bSuccess && !bComplete)
{
PromptResult dragres = ed.Drag(jig);
bSuccess =
(dragres.Status == PromptStatus.OK);
if (bSuccess)
jig.AddVertex();
bComplete =
(dragres.Status == PromptStatus.None);
if (bComplete)
jig.RemoveLastVertex();
}
if (bComplete)
{
// Append entity
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead,
false
);
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace],
OpenMode.ForWrite,
false
);
btr.AppendEntity(jig.GetEntity());
tr.AddNewlyCreatedDBObject(
jig.GetEntity(),
true
);
tr.Commit();
}
}
}
}
}
}
Here's what you get when you run the MYML command:
September 21, 2007 in AutoCAD, AutoCAD .NET, Dimensions, Jigs | Permalink | Comments (5) | TrackBack
Heading for the hills
Well, through them, anyway.
Just letting you know that I'll be away from my desk, and from this blog, for the next week or so. We're heading through the Alps to Italy: first to Milan and then down to Ascoli Piceno, where an old friend is getting married.
We decided to take the train, which should be interesting. It's 4 hours from Switzerland to Milan, plus another 5 or so hours from Milan to the rail hub nearest to Ascoli P. Luckily we have family in Milan and so get to spend time with them and break the journey in each direction.
Back in a week!
September 12, 2007 in Personal | Permalink | Comments (0) | TrackBack
Accessing the active space or layout in an AutoCAD drawing using .NET
This question was asked as comment to a previous post by har!s:
Thanks a lot for the code. I have yet to see 2008 and MultiLeader. But I presume that it works on both Model and paper spaces. In that case, what is the best method to make the operation space independent? i.e., it should work on active space irrespective of whether it's model or paper. I think this will be generally applicable to almost all the entity creations.
The question is very valid and does indeed apply to a lot of entity creation - and other - activities. Most of the time I simply show how to open the modelspace in my code, for example:
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace],
OpenMode.ForWrite
);
// ...
}
The key statement here is at the end, where we use GetObject() to open the BlockTableRecord to which we want to (for example) append an entity. The form we use is:
bt[BlockTableRecord.ModelSpace]
Breaking this down: we're actually looking up the ObjectId of the BlockTableRecord with the name of "*MODEL_SPACE", which is the string stored in the static ModelSpace property of the BlockTableRecord class.
Here are a few different options for what we might do here:
- Use either BlockTableRecord.ModelSpace or BlockTableRecord.PaperSpace, if we know that we want to access either of these containers (the current approach).
- Use foreach() on the BlockTable to iterate through the various BlockTableRecords: you

Atom


