AutoCAD has two kinds of drawing spaces, model space and paper spaces, as we know. The model space has one and only one in AutoCAD represented by the single Model tab but the paper spaces can have many represented by the multiple LayoutN tabs. People commonly work in the default model space most times, so do programmers. However, that does not prevent anybody from invoking any commands to try to create something into a paper space. AutoCAD commands generally handle both spaces well, meaning when we run the CIRCLE command, for example, in the model tab the circle will be added to the mode space; in a layout tab it go to the particular paper space.
Therefore, as AutoCAD programmers we would better consider this as well. Otherwise, it would look odd that something is added to the model tab when users try our commands in one of paper space layouts. It would be weirder that the entity will appear nowhere in the visible paper drawing area (instead it wrongly appears in the model tab) after quite something has been jigged about it in the current paper space.
Let us create a simple command first which can create a circle always into Model Space.
[CommandMethod("CreateCircleInMS")]
public void Test_CreateCircleInMS()
{
Database db = HostApplicationServices.WorkingDatabase;
Editor ed = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
Matrix3d ucs = ed.CurrentUserCoordinateSystem;
try
{
PromptPointOptions prOpt = new PromptPointOptions("\nCircle center: ");
PromptPointResult pr = ed.GetPoint(prOpt);
if (pr.Status != PromptStatus.OK) return;
PromptDistanceOptions prOpt1 = new PromptDistanceOptions("\nCircle radius: ");
prOpt1.UseBasePoint = true;
prOpt1.UseDashedLine = true;
prOpt1.BasePoint = pr.Value;
PromptDoubleResult pr1 = ed.GetDistance(prOpt1);
if (pr1.Status != PromptStatus.OK) return;
using (Circle cir = new Circle(pr.Value, Vector3d.ZAxis, pr1.Value))
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
cir.TransformBy(ed.CurrentUserCoordinateSystem);
btr.AppendEntity(cir);
tr.AddNewlyCreatedDBObject(cir, true);
tr.Commit();
}
}
}
catch (System.Exception ex)
{
ed.WriteMessage(ex.ToString());
}
}
The command works perfectly well in Model Space as expected. However, when the same command is tried in a paper space, the circle appears nowhere in the paper space itself. Instead, it would be added to the model space with the collected circle center coordinates and radius distance interpreted in the paper space coordinate system. If we check the circle in the model space, it may look like the following:
As can be seen, not only its center and radius do not look good but also its shape. That is obviously because of the UCS transformation we do in the code, as broadly speaking a Paper Space coordinate system is also a kind of UCS.
Let us create another simple command next which can create a circle always into Paper Space.
[CommandMethod("CreateCircleInPS")]
public void Test_CreateCircleInPS()
{
Database db = HostApplicationServices.WorkingDatabase;
Editor ed = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
Matrix3d ucs = ed.CurrentUserCoordinateSystem;
try
{
PromptPointOptions prOpt = new PromptPointOptions("\nCircle center: ");
PromptPointResult pr = ed.GetPoint(prOpt);
if (pr.Status != PromptStatus.OK) return;
PromptDistanceOptions prOpt1 = new PromptDistanceOptions("\nCircle radius: ");
prOpt1.UseBasePoint = true;
prOpt1.UseDashedLine = true;
prOpt1.BasePoint = pr.Value;
PromptDoubleResult pr1 = ed.GetDistance(prOpt1);
if (pr1.Status != PromptStatus.OK) return;
using (Circle cir = new Circle(pr.Value, Vector3d.ZAxis, pr1.Value))
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.PaperSpace], OpenMode.ForWrite);
cir.TransformBy(ed.CurrentUserCoordinateSystem);
btr.AppendEntity(cir);
tr.AddNewlyCreatedDBObject(cir, true);
tr.Commit();
}
}
}
catch (System.Exception ex)
{
ed.WriteMessage(ex.ToString());
}
}
The command works perfectly well in any Paper Space as expected. However, when the same command is tried in Model Space, the circle appears nowhere in the model view itself. Instead, it would be added to one of paper spaces with the collected circle center coordinates and radius distance interpreted in the coordinate system either WCS or UCS of Model Space.
In terms of which exact Paper Space that would be it is hard to say. Through some experiments though, it is found out that the paper space should be associated with the layout tab from where we switch back to the current Model tab. That means we have to keep hard in memory that which the last active layout tab it is if we choose to add entities to paper space like this.
If we check the circle in the paper space (if we can clearly remember which exact layout represents it), the circle may look like the following:
As can be seen, not only its center and radius do not look good but also it becomes an ellipsis. That is obviously because of the UCS transformation we do in the code, as broadly speaking a Paper Space coordinate system is also a kind of UCS.
Now let us create the final and good command that can create a circle and append it to the right Space (either Model or Paper) and more importantly to the right Paper Space of concern instead of kinda random.
[CommandMethod("CreateCircleInCurrent")]
public void Test_CreateCircleInCurrent()
{
Database db = HostApplicationServices.WorkingDatabase;
Editor ed = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
Matrix3d ucs = ed.CurrentUserCoordinateSystem;
try
{
PromptPointOptions prOpt = new PromptPointOptions("\nCircle center: ");
PromptPointResult pr = ed.GetPoint(prOpt);
if (pr.Status != PromptStatus.OK) return;
PromptDistanceOptions prOpt1 = new PromptDistanceOptions("\nCircle radius: ");
prOpt1.UseBasePoint = true;
prOpt1.UseDashedLine = true;
prOpt1.BasePoint = pr.Value;
PromptDoubleResult pr1 = ed.GetDistance(prOpt1);
if (pr1.Status != PromptStatus.OK) return;
using (Circle cir = new Circle(pr.Value, Vector3d.ZAxis, pr1.Value))
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
cir.TransformBy(ed.CurrentUserCoordinateSystem);
btr.AppendEntity(cir);
tr.AddNewlyCreatedDBObject(cir, true);
tr.Commit();
}
}
}
catch (System.Exception ex)
{
ed.WriteMessage(ex.ToString());
}
}
If we try it out in model space and different paper spaces, we will find that it behave all well.
So, another good practice can be concluded. Please use the Database.CurrentSpaceId to open the BlockTableRecord and add the entity to the current space instead of model space or any paper space. In this way, not only the entity creation will behave well for both model space and paper spaces, but also it saves us some lines of code as demonstrated.
If we really want the command to work in only Model Space or Paper Space but not both, we’d better do some checking to see whether the current space is model or paper to determine what to do next. Some command flags can help us do so automatically, such as the CommandFlags.NoPaperSpace and the CommandFlags.NoTileMode as introduced before in some other articles.
Enjoy, Guys! More coding gadgets and good practices regarding AutoCAD .NET programming can be expected in the future. Stay tuned.
The leading edge AutoCAD .NET Addin Wizard (AcadNetAddinWizard) exercises the good practice in its various wizards, coders and widgets when applicable.
Posted by: |