明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索

[Kean专集] Kean专题(9)—Object_Properties

   关闭 [复制链接]
 楼主| 发表于 2009-6-1 11:20 | 显示全部楼层
九、建立一个包含文字的复杂线型
January 09, 2008
Creating a complex AutoCAD linetype containing text using .NET
In my last post we saw some code to create a simple linetype using .NET. As a comment on that post, Mark said:
Kean, i tried you code and it works great and it also got me thinking... is it possible to programmitically add text in as well? I've tried using ltr.SetTextAt(1, "TEST") but so far i've had no luck, any suggestions???
It turned out to be quite a bit more complicated to make a linetype containing text than merely calling SetTextAt() on one of the segments. In order to understand what properties needed setting, I first loaded the HOT_WATER_SUPPLY linetype from acad.lin (using the LINETYPE command):

I then looked at the contents of the linetype table using ArxDbg (the ObjectARX SDK sample that is very helpful for understanding drawing structure). Here's what the SNOOPDB command - defined by the ArxDbg application - showed for the loaded linetype:
From there it was fairly straightforward to determine the code needed to create our own complex linetype containing text segments. I decided to call the new linetype "COLD_WATER_SUPPLY", and have it resemble the original in every way but placing "CW" in the middle segment, rather than "HW" (with the descriptions updated to match, of course). As I've simply copied the properties of an existing linetype, please don't ask me to explain what they all mean. :-)
Here's the C# code:
  1. using Autodesk.AutoCAD.Runtime;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.Geometry;
  5. using Autodesk.AutoCAD.EditorInput;
  6. namespace Linetype
  7. {
  8.   public class Commands
  9.   {
  10.     [CommandMethod("CCL")]
  11.     public void CreateComplexLinetype()
  12.     {
  13.       Document doc =
  14.         Application.DocumentManager.MdiActiveDocument;
  15.       Database db = doc.Database;
  16.       Editor ed = doc.Editor;
  17.       Transaction tr =
  18.         db.TransactionManager.StartTransaction();
  19.       using (tr)
  20.       {
  21.         // We'll use the textstyle table to access
  22.         // the "Standard" textstyle for our text
  23.         // segment
  24.         TextStyleTable tt =
  25.           (TextStyleTable)tr.GetObject(
  26.             db.TextStyleTableId,
  27.             OpenMode.ForRead
  28.           );
  29.         // Get the linetype table from the drawing
  30.         LinetypeTable lt =
  31.           (LinetypeTable)tr.GetObject(
  32.             db.LinetypeTableId,
  33.             OpenMode.ForWrite
  34.           );
  35.         // Create our new linetype table record...
  36.         LinetypeTableRecord ltr =
  37.           new LinetypeTableRecord();
  38.         // ... and set its properties
  39.         ltr.Name = "COLD_WATER_SUPPLY";
  40.         ltr.AsciiDescription =
  41.           "Cold water supply ---- CW ---- CW ---- CW ----";
  42.         ltr.PatternLength = 0.9;
  43.         ltr.NumDashes = 3;
  44.         // Dash #1
  45.         ltr.SetDashLengthAt(0, 0.5);
  46.         // Dash #2
  47.         ltr.SetDashLengthAt(1, -0.2);
  48.         ltr.SetShapeStyleAt(1, tt["Standard"]);
  49.         ltr.SetShapeNumberAt(1, 0);
  50.         ltr.SetShapeOffsetAt(1, new Vector2d(-0.1,-0.05));
  51.         ltr.SetShapeScaleAt(1, 0.1);
  52.         ltr.SetShapeIsUcsOrientedAt(1, false);
  53.         ltr.SetShapeRotationAt(1, 0);
  54.         ltr.SetTextAt(1, "CW");
  55.         // Dash #3
  56.         ltr.SetDashLengthAt(2, -0.2);
  57.         // Add the new linetype to the linetype table
  58.         ObjectId ltId = lt.Add(ltr);
  59.         tr.AddNewlyCreatedDBObject(ltr, true);
  60.         // Create a test line with this linetype
  61.         BlockTable bt =
  62.           (BlockTable)tr.GetObject(
  63.             db.BlockTableId,
  64.             OpenMode.ForRead
  65.           );
  66.         BlockTableRecord btr =
  67.           (BlockTableRecord)tr.GetObject(
  68.             bt[BlockTableRecord.ModelSpace],
  69.             OpenMode.ForWrite
  70.           );
  71.         Line ln =
  72.           new Line(
  73.             new Point3d(0, 0, 0),
  74.             new Point3d(10, 10, 0)
  75.           );
  76.         ln.SetDatabaseDefaults(db);
  77.         ln.LinetypeId = ltId;
  78.         btr.AppendEntity(ln);
  79.         tr.AddNewlyCreatedDBObject(ln, true);
  80.         tr.Commit();
  81.       }
  82.     }
  83.   }
  84. }
And here's the result of calling the CCL command and zooming in on the created line:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-6-1 11:21 | 显示全部楼层

January 11, 2008
Understanding the properties of textual linetype segments in AutoCAD
In the last post we looked at using .NET to define complex linetypes containing text segments. In the post I admitted to not knowing specifics about the properties used to create the text segment in the linetype, and, in the meantime, an old friend took pity on me and came to the rescue. :-)

Mike Kehoe, who I've known for many years since we worked together in the Guildford office of Autodesk UK, sent me some information that I've reproduced below. Mike now works for Micro Concepts Ltd., an Autodesk reseller, developer and training centre. He originally wrote the below description in the R12/12 timeframe, but apparently most of it remains valid; and while it refers to the text string used to define a linetype in a .lin file, these are also mostly properties that are exposed via the .NET interface.

Example: Using Text within a Linetype.
A,.5,-.2,["MK",STANDARD,S=.2,R=0.0,X=-0.1,Y=-.1],-.2

The key elements for defining the TEXT are as follows:

"MK" - These are the letters that will be printed along the line.

STANDARD -This tells AutoCAD what text style to apply to the text.  NB: This is optional. When no style is defined AutoCAD will use the current text style – TextStyle holds the setting for the current text style.

[Note from Kean: I found the text style to be mandatory when using the .NET interface.]

S=.2 - This is the text scaling factor. However, there are 2 options: (1) when the text style's height is 0, then S defines the height; in this case, 0.2 units; or (2) when the text style's height parameter is non-zero, the height is found by multiplying the text style's height by this number; in this case, the linetype would place the text at 20% of the height defined in the text style.

R=0.0 - This rotates the text relative to the direction of the line; e.g.: 0.0 means there is no rotation. NB: This is optional. When no rotation is defined AutoCAD will assume zero degrees. The default measurement is degrees; NB: you can use r to specify radians, g for grads, or d for degrees, such as R=150g.

[Note from Kean: just like ObjectARX, the .NET interface accepts radians for this value, in SetShapeRotationAt(). A quick reminder: 360 degrees = 2 x PI radians. So you can pass 90 degrees using "System.Math.PI / 2".]

A=0.0  - This rotates the text relative to the x-axis ("A" is short for Absolute); this ensures the text is always oriented in the same direction, no matter the direction of the line. The rotation is always performed within the text baseline and capital height. That's so that you don't get text rotated way off near the orbit of Pluto.

[Note from Kean: to use this style of rotation using .NET, you need to use SetShapeIsUcsOrientedAt() to make sure the rotation is calculated relative to the current UCS rather than the direction of the line.]

X=-0.1 - This setting moves the text just in the x-direction from the linetype definition vertex.

Y=-0.1 – This setting moves the text in the y-direction from the linetype definition vertex.
These 2 settings can be used to center the text in the line. The units are defined from the linetype scale factor, which is stored in system variable LtScale.

Thanks for the information, Mike!

 楼主| 发表于 2009-6-5 14:01 | 显示全部楼层
本帖最后由 作者 于 2009-6-7 15:39:46 编辑

十、使用AutoCad的颜色、线型、线宽对话框
February 08, 2008
Using standard AutoCAD dialogs to select colors, linetypes and lineweights with .NET
AutoCAD has a number of handy dialogs available in the "Autodesk.AutoCAD.Windows" namespace. The following code shows how to use three, in particular: ColorDialog, LinetypeDialog and LineWeightDialog. These three classes allow you to very easily implement user-interfaces selecting their corresponding properties.
Here's the C# code:
  1. using Autodesk.AutoCAD.Runtime;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.EditorInput;
  5. using Autodesk.AutoCAD.Windows;
  6. namespace AutoCADDialogs
  7. {
  8.   public class Commands
  9.   {
  10.     [CommandMethod("CDL")]
  11.     public void ShowColorDialog()
  12.     {
  13.       Document doc =
  14.         Application.DocumentManager.MdiActiveDocument;
  15.       Database db = doc.Database;
  16.       Editor ed = doc.Editor;
  17.       ColorDialog cd = new ColorDialog();
  18.       System.Windows.Forms.DialogResult dr =
  19.         cd.ShowDialog();
  20.       if (dr == System.Windows.Forms.DialogResult.OK)
  21.       {
  22.         ed.WriteMessage(
  23.           "\nColor selected: " +
  24.           cd.Color.ToString()
  25.         );
  26.       }
  27.     }
  28.     [CommandMethod("LTDL")]
  29.     public void ShowLinetypeDialog()
  30.     {
  31.       Document doc =
  32.         Application.DocumentManager.MdiActiveDocument;
  33.       Database db = doc.Database;
  34.       Editor ed = doc.Editor;
  35.       LinetypeDialog ltd = new LinetypeDialog();
  36.       System.Windows.Forms.DialogResult dr =
  37.         ltd.ShowDialog();
  38.       if (dr == System.Windows.Forms.DialogResult.OK)
  39.       {
  40.         ed.WriteMessage(
  41.           "\nLinetype selected: " +
  42.           ltd.Linetype.ToString()
  43.         );
  44.       }
  45.     }
  46.     [CommandMethod("LWDL")]
  47.     public void ShowLineWeightDialog()
  48.     {
  49.       Document doc =
  50.         Application.DocumentManager.MdiActiveDocument;
  51.       Database db = doc.Database;
  52.       Editor ed = doc.Editor;
  53.       LineWeightDialog lwd = new LineWeightDialog();
  54.       System.Windows.Forms.DialogResult dr =
  55.         lwd.ShowDialog();
  56.       if (dr == System.Windows.Forms.DialogResult.OK)
  57.       {
  58.         ed.WriteMessage(
  59.           "\nLineweight selected: " +
  60.           lwd.LineWeight.ToString()
  61.         );
  62.       }
  63.     }
  64.   }
  65. }
When we run the CDL command, we have three tabs available:



Here's the dialog shown by the LTDL command:

And the LWDL command:

And here is the command-line output for selecting each of the above items:
  1. Command: CDL
  2. Color selected: 91
  3. Command: CDL
  4. Color selected: 56,166,199
  5. Command: CDL
  6. Color selected: DIC 266
  7. Command: LTDL
  8. Linetype selected: (2130316464)
  9. Command: LWDL
  10. Lineweight selected: LineWeight009
复制代码
In the next post we'll look at at how to use these dialogs in a more realistic way.


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-6-5 14:02 | 显示全部楼层
February 12, 2008
Modifying the color, linetype and lineweight of an AutoCAD entity using standard dialogs from .NET
In the last post we saw code to display and use AutoCAD's built-in colour, linetype and lineweight dialogs. In this post we extend that by using each of them in sequence to display various properties of an entity, allowing the user to modify them.
While this is slightly more "real world" than the last post, it doesn't really make sense to implement a command such as the one below (the property palette is much better, for instance :-). The main purpose is to show how to set the initial values in the various dialogs and afterwards to make use of the values the user selects.
Here's the C# code:
  1. using Autodesk.AutoCAD.Runtime;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.EditorInput;
  5. using Autodesk.AutoCAD.Windows;
  6. namespace EntityProperties
  7. {
  8.   public class Commands
  9.   {
  10.     [CommandMethod("SPE")]
  11.     public void SetPropertiesOnEntity()
  12.     {
  13.       Document doc =
  14.         Application.DocumentManager.MdiActiveDocument;
  15.       Database db = doc.Database;
  16.       Editor ed = doc.Editor;
  17.       PromptEntityResult per =
  18.         ed.GetEntity(
  19.           "\nSelect entity to modify: "
  20.         );
  21.       if (per.Status != PromptStatus.OK)
  22.         return;
  23.       Transaction tr =
  24.         db.TransactionManager.StartTransaction();
  25.       using (tr)
  26.       {
  27.         Entity ent =
  28.           (Entity)
  29.             tr.GetObject(
  30.               per.ObjectId,
  31.               OpenMode.ForRead
  32.             );
  33.         ColorDialog cd = new ColorDialog();
  34.         cd.Color = ent.Color;
  35.         System.Windows.Forms.DialogResult dr;
  36.         dr = cd.ShowDialog();
  37.         if (dr != System.Windows.Forms.DialogResult.OK)
  38.           return;
  39.         LinetypeDialog ltd = new LinetypeDialog();
  40.         ltd.Linetype = ent.LinetypeId;
  41.         dr = ltd.ShowDialog();
  42.         if (dr != System.Windows.Forms.DialogResult.OK)
  43.           return;
  44.         LineWeightDialog lwd = new LineWeightDialog();
  45.         lwd.LineWeight = ent.LineWeight;
  46.         dr = lwd.ShowDialog();
  47.         if (dr != System.Windows.Forms.DialogResult.OK)
  48.           return;
  49.         ent.UpgradeOpen();
  50.         if (ent.Color != cd.Color)
  51.           ent.Color = cd.Color;
  52.         if (ent.LinetypeId != ltd.Linetype)
  53.           ent.LinetypeId = ltd.Linetype;
  54.         if (ent.LineWeight != lwd.LineWeight)
  55.           ent.LineWeight = lwd.LineWeight;
  56.         tr.Commit();
  57.       }
  58.     }
  59.   }
  60. }
Notice that we "return" from the code in a number of places, should the user cancel one of the dialogs. This is perfectly safe, as we're "using" the transaction object, and once it leaves scope it will automatically be disposed (and therefore aborted).
Running the SPE command will display in sequence the dialogs shown in the previous post, and use the user's input to change the state of the selected entity.

 楼主| 发表于 2009-6-5 14:03 | 显示全部楼层
本帖最后由 作者 于 2009-6-7 16:10:16 编辑

十一、在AutoCad2009中使用新的.NetApi从文件中提取XML数据
April 07, 2008
Extracting XML data from drawings using a new .NET API in AutoCAD 2009
This post is the latest in the series of closer looks at the new APIs in AutoCAD 2009. It covers the Data Extraction API, a new .NET API allowing you to drive the Data Extraction feature inside AutoCAD.
There is a very thorough C# sample included on the ObjectARX SDK (under samples/dotNet/DataExtraction), so I thought I'd focus on a much simpler example where we extract data from a single drawing.
Here's the C# code:
  1. using System.Collections.Generic;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.DataExtraction;
  6. namespace DataExtraction
  7. {
  8.   public class Commands
  9.   {
  10.     const string path =
  11.       @"c:\Program Files\Autodesk\AutoCAD 2009\Sample";
  12.     const string fileName =
  13.       "Visualization - Aerial.dwg";
  14.     const string outputXmlFile =
  15.       @"c:\temp\data-extract.xml";
  16.     [CommandMethod("extd")]
  17.     public void extractData()
  18.     {
  19.       if (!System.IO.File.Exists(path + fileName))
  20.       {
  21.         Document doc =
  22.           Application.DocumentManager.MdiActiveDocument;
  23.         Editor ed =
  24.           doc.Editor;
  25.         ed.WriteMessage("\nFile does not exist.");
  26.         return;
  27.       }
  28.       // Create some settings for the extraction
  29.       IDxExtractionSettings es =
  30.         new DxExtractionSettings();
  31.       IDxDrawingDataExtractor de =
  32.         es.DrawingDataExtractor;
  33.       de.Settings.ExtractFlags =
  34.         ExtractFlags.ModelSpaceOnly |
  35.         ExtractFlags.XrefDependent |
  36.         ExtractFlags.Nested;
  37.       // Add a single file to the settings
  38.       IDxFileReference fr =
  39.         new DxFileReference(path, path + fileName);
  40.       de.Settings.DrawingList.AddFile(fr);
  41.       // Scan the drawing for object types & their properties
  42.       de.DiscoverTypesAndProperties(path);
  43.       List<IDxTypeDescriptor> types =
  44.         de.DiscoveredTypesAndProperties;
  45.       // Select all the types and properties for extraction
  46.       // by adding them one-by-one to these two lists
  47.       List<string> selTypes = new List<string>();
  48.       List<string> selProps = new List<string>();
  49.       foreach (IDxTypeDescriptor type in types)
  50.       {
  51.         selTypes.Add(type.GlobalName);
  52.         foreach (
  53.           IDxPropertyDescriptor pr in type.Properties
  54.         )
  55.         {
  56.           if (!selProps.Contains(pr.GlobalName))
  57.             selProps.Add(pr.GlobalName);
  58.         }
  59.       }
  60.       // Pass this information to the extractor
  61.       de.Settings.SetSelectedTypesAndProperties(
  62.         types,
  63.         selTypes,
  64.         selProps
  65.       );
  66.       // Now perform the extraction itself
  67.       de.ExtractData(path);
  68.       // Get the results of the extraction
  69.       System.Data.DataTable dataTable =
  70.         de.ExtractedData;
  71.       // Output the extracted data to an XML file
  72.       if (dataTable.Rows.Count > 0)
  73.       {
  74.         dataTable.TableName = "My_Data_Extract";
  75.         dataTable.WriteXml(outputXmlFile);
  76.       }
  77.     }
  78.   }
  79. }
Here are the first few objects from the output file found in c:\temp\data-extract.xml after running the EXTD command:
  1. <?xml version="1.0" standalone="yes"?>
  2. <DocumentElement>
  3.   <My_Data_Extract>
  4.     <AcDxHandleData>183</AcDxHandleData>
  5.     <Layer>0</Layer>
  6.     <LinetypeScale>1</LinetypeScale>
  7.     <PlotStyleName>ByLayer</PlotStyleName>
  8.     <LineWeight>ByLayer</LineWeight>
  9.     <Material>Material 3</Material>
  10.     <Linetype>ByLayer</Linetype>
  11.     <Color>ByLayer</Color>
  12.     <AcDxObjectTypeName>3D Solid</AcDxObjectTypeName>
  13.     <AcDxObjectTypeGlobalName>
  14.       Autodesk.AutoCAD.DatabaseServices.Solid3d
  15.     </AcDxObjectTypeGlobalName>
  16.     <AcDxDwgSummaryDwgName>
  17.       Visualization - Aerial.dwg
  18.     </AcDxDwgSummaryDwgName>
  19.     <AcDxDwgSummaryDwgLocation>
  20.       c:\Program Files\Autodesk\AutoCAD 2009\Sample
  21.     </AcDxDwgSummaryDwgLocation>
  22.     <AcDxDwgSummaryDwgSize>472480</AcDxDwgSummaryDwgSize>
  23.     <AcDxDwgSummaryDwgCreated>
  24.       2007-01-03T00:44:20+01:00
  25.     </AcDxDwgSummaryDwgCreated>
  26.     <AcDxDwgSummaryDwgModified>
  27.       2007-01-03T00:44:20+01:00
  28.     </AcDxDwgSummaryDwgModified>
  29.     <AcDxDwgSummaryDwgAccessed>
  30.       2008-03-03T11:40:53.796875+01:00
  31.     </AcDxDwgSummaryDwgAccessed>
  32.     <AcDxDwgSummaryDwgTitle />
  33.     <AcDxDwgSummaryDwgSubject />
  34.     <AcDxDwgSummaryDwgAuthor />
  35.     <AcDxDwgSummaryDwgKeywords />
  36.     <AcDxDwgSummaryDwgComments />
  37.     <AcDxDwgSummaryDwgHyperLinkBase />
  38.     <AcDxDwgSummaryDwgLastSavedBy>
  39.       thompsl
  40.     </AcDxDwgSummaryDwgLastSavedBy>
  41.     <AcDxDwgSummaryDwgRevisionNumber />
  42.     <AcDxDwgSummaryDwgTotalEditingTime>
  43.       1179
  44.     </AcDxDwgSummaryDwgTotalEditingTime>
  45.     <AcDxEntityHyperlink />
  46.   </My_Data_Extract>
  47.   <My_Data_Extract>
  48.     <AcDxHandleData>191</AcDxHandleData>
  49.     <Layer>0</Layer>
  50.     <LinetypeScale>1</LinetypeScale>
  51.     <PlotStyleName>ByLayer</PlotStyleName>
  52.     <LineWeight>ByLayer</LineWeight>
  53.     <Material>Material 3</Material>
  54.     <Linetype>ByLayer</Linetype>
  55.     <Color>ByLayer</Color>
  56.     <AcDxObjectTypeName>3D Solid</AcDxObjectTypeName>
  57.     <AcDxObjectTypeGlobalName>
  58.       Autodesk.AutoCAD.DatabaseServices.Solid3d
  59.     </AcDxObjectTypeGlobalName>
  60.     <AcDxDwgSummaryDwgName>
  61.       Visualization - Aerial.dwg
  62.     </AcDxDwgSummaryDwgName>
  63.     <AcDxDwgSummaryDwgLocation>
  64.       c:\Program Files\Autodesk\AutoCAD 2009\Sample
  65.     </AcDxDwgSummaryDwgLocation>
  66.     <AcDxDwgSummaryDwgSize>472480</AcDxDwgSummaryDwgSize>
  67.     <AcDxDwgSummaryDwgCreated>
  68.       2007-01-03T00:44:20+01:00
  69.     </AcDxDwgSummaryDwgCreated>
  70.     <AcDxDwgSummaryDwgModified>
  71.       2007-01-03T00:44:20+01:00
  72.     </AcDxDwgSummaryDwgModified>
  73.     <AcDxDwgSummaryDwgAccessed>
  74.       2008-03-03T11:40:53.796875+01:00
  75.     </AcDxDwgSummaryDwgAccessed>
  76.     <AcDxDwgSummaryDwgTitle />
  77.     <AcDxDwgSummaryDwgSubject />
  78.     <AcDxDwgSummaryDwgAuthor />
  79.     <AcDxDwgSummaryDwgKeywords />
  80.     <AcDxDwgSummaryDwgComments />
  81.     <AcDxDwgSummaryDwgHyperLinkBase />
  82.     <AcDxDwgSummaryDwgLastSavedBy>
  83.       thompsl
  84.     </AcDxDwgSummaryDwgLastSavedBy>
  85.     <AcDxDwgSummaryDwgRevisionNumber />
  86.     <AcDxDwgSummaryDwgTotalEditingTime>
  87.       1179
  88.     </AcDxDwgSummaryDwgTotalEditingTime>
  89.     <AcDxEntityHyperlink />
  90.   </My_Data_Extract>
  91.   <!-- Stuff deleted from the XML output -->
  92.   <!-- ... -->
  93. </DocumentElement>

 楼主| 发表于 2009-6-5 14:05 | 显示全部楼层
本帖最后由 作者 于 2009-6-7 16:11:06 编辑

十二、图层过滤器
July 11, 2008
Adding and removing AutoCAD layer filters using .NET
This question came in by email last week:
The last two days I am struggling with adding a new LayerGroup to HostApplicationServices.WorkingDatabase.LayerFilters.Root. I simply cannot get it working. Is it possible to write a blog item with example on this?
An interesting question and it can indeed be quite tricky to understand how this works. For those of you who are ADN members, here's a DevNote covering the basics, which I've taken and extended for this post.
When working with LayerFilters, it's important to know that the Database property providing access to the list of current LayerFilters does so by value (i.e. you have to get a copy of the current set of LayerFilters, manipulate it, and then set it back on the Database object to have them picked up by the system).
The below C# code implements commands to list the existing layer filters (LLFS), create three new layer filters (CLFS), and delete a layer filter selected by the user (DLF):
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.LayerManager;
  6. namespace LayerFilters
  7. {
  8.   public class Commands
  9.   {
  10.     [CommandMethod("LLFS")]
  11.     static public void ListLayerFilters()
  12.     {
  13.       Document doc =
  14.         Application.DocumentManager.MdiActiveDocument;
  15.       Database db = doc.Database;
  16.       Editor ed = doc.Editor;
  17.       // List the nested layer filters
  18.       LayerFilterCollection lfc =
  19.         db.LayerFilters.Root.NestedFilters;
  20.       for (int i = 0; i < lfc.Count; ++i)
  21.       {
  22.         LayerFilter lf = lfc[i];
  23.         ed.WriteMessage(
  24.           "\n{0} - {1} (can{2} be deleted)",
  25.           i + 1,
  26.           lf.Name,
  27.           (lf.AllowDelete ? "" : "not")
  28.         );
  29.       }
  30.     }
  31.     [CommandMethod("CLFS")]
  32.     static public void CreateLayerFilters()
  33.     {
  34.       Document doc =
  35.         Application.DocumentManager.MdiActiveDocument;
  36.       Database db = doc.Database;
  37.       Editor ed = doc.Editor;
  38.       try
  39.       {
  40.         // Get the existing layer filters
  41.         // (we will add to them and set them back)
  42.         LayerFilterTree lft =
  43.           db.LayerFilters;
  44.         LayerFilterCollection lfc =
  45.           lft.Root.NestedFilters;
  46.         // Create three new layer filters
  47.         LayerFilter lf1 = new LayerFilter();
  48.         lf1.Name = "Unlocked Layers";
  49.         lf1.FilterExpression = "LOCKED=="False"";
  50.         LayerFilter lf2 = new LayerFilter();
  51.         lf2.Name = "White Layers";
  52.         lf2.FilterExpression = "COLOR=="7"";
  53.         LayerFilter lf3 = new LayerFilter();
  54.         lf3.Name = "Visible Layers";
  55.         lf3.FilterExpression =
  56.           "OFF=="False" AND FROZEN=="False"";
  57.         // Add them to the collection
  58.         lfc.Add(lf1);
  59.         lfc.Add(lf2);
  60.         lfc.Add(lf3);
  61.         // Set them back on the Database
  62.         db.LayerFilters = lft;
  63.         // List the layer filters, to see the new ones
  64.         ListLayerFilters();
  65.       }
  66.       catch (Exception ex)
  67.       {
  68.         ed.WriteMessage(
  69.           "\nException: {0}",
  70.           ex.Message
  71.         );
  72.       }
  73.     }
  74.     [CommandMethod("DLF")]
  75.     static public void DeleteLayerFilter()
  76.     {
  77.       Document doc =
  78.         Application.DocumentManager.MdiActiveDocument;
  79.       Database db = doc.Database;
  80.       Editor ed = doc.Editor;
  81.       ListLayerFilters();
  82.       try
  83.       {
  84.         // Get the existing layer filters
  85.         // (we will add to them and set them back)
  86.         LayerFilterTree lft =
  87.           db.LayerFilters;
  88.         LayerFilterCollection lfc =
  89.           lft.Root.NestedFilters;
  90.         // Prompt for the index of the filter to delete
  91.         PromptIntegerOptions pio =
  92.           new PromptIntegerOptions(
  93.           "\n\nEnter index of filter to delete"
  94.           );
  95.         pio.LowerLimit = 1;
  96.         pio.UpperLimit = lfc.Count;
  97.         PromptIntegerResult pir =
  98.           ed.GetInteger(pio);
  99.         // Get the selected filter
  100.         LayerFilter lf = lfc[pir.Value - 1];
  101.         // If it's possible to delete it, do so
  102.         if (!lf.AllowDelete)
  103.         {
  104.           ed.WriteMessage(
  105.             "\nLayer filter cannot be deleted."
  106.           );
  107.         }
  108.         else
  109.         {
  110.           lfc.Remove(lf);
  111.           db.LayerFilters = lft;
  112.           ListLayerFilters();
  113.         }
  114.       }
  115.       catch(Exception ex)
  116.       {
  117.         ed.WriteMessage(
  118.           "\nException: {0}",
  119.           ex.Message
  120.         );
  121.       }
  122.     }
  123.   }
  124. }
Here's what happens when we run the various commands:
  1. Command: LLFS
  2. 1 - All Used Layers (cannot be deleted)
  3. 2 - Unreconciled New Layers (cannot be deleted)
  4. 3 - Viewport Overrides (cannot be deleted)
  5. Command: CLFS
  6. 1 - Unlocked Layers (can be deleted)
  7. 2 - White Layers (can be deleted)
  8. 3 - Visible Layers (can be deleted)
  9. 4 - All Used Layers (cannot be deleted)
  10. 5 - Unreconciled New Layers (cannot be deleted)
  11. 6 - Viewport Overrides (cannot be deleted)
  12. Command: DLF
  13. 1 - Unlocked Layers (can be deleted)
  14. 2 - White Layers (can be deleted)
  15. 3 - Visible Layers (can be deleted)
  16. 4 - All Used Layers (cannot be deleted)
  17. 5 - Unreconciled New Layers (cannot be deleted)
  18. 6 - Viewport Overrides (cannot be deleted)
  19. Enter index of filter to delete: 1
  20. 1 - White Layers (can be deleted)
  21. 2 - Visible Layers (can be deleted)
  22. 3 - All Used Layers (cannot be deleted)
  23. 4 - Unreconciled New Layers (cannot be deleted)
  24. 5 - Viewport Overrides (cannot be deleted)
复制代码
You can see the three new filters (before you delete any with the DLF command) in the LAYER dialog. To see them work, create a bunch of layers, changing the colour of some and turning off/freezing/locking others:

 楼主| 发表于 2009-6-7 16:08 | 显示全部楼层
September 24, 2008
Creating a layer group inside AutoCAD using .NET
The following question came in as a comment on this previous post:
Your example above shows how to create a property type filter, how do you add layers to a group type filter (LayerGroup class)? I can create the group filter but can't figure out how to add layers to the group. The filterexpression method is available but doesn't seem to work (at least not like with the LayerFilter object)
To tackle this, let's start by looking at the documentation on the LayerGroup class in the .NET Reference (currently part of the ObjectARX Reference):
LayerGroup is derived from LayerFilter and serves as the access to layer group filters. It allows the client to specify and retrieve a set of layer IDs. The filter() method returns true if the object ID of the given layer is contained in the set of layer IDs for the LayerGroup.
Specifying the filter criteria is done solely by using LayerId. LayerGroup doesn't use a filter expression string, so FilterExpression and FilterExpressionTree return a null pointer.
The recommended way of identifying LayerGroup filters in a group of layer filters is to query the IsIdFilter property, which returns true for an LayerGroup.
So, at the root of the question, we need to create a LayerGroup and add the ObjectIds of the LayerTableRecords we wish to include in the group to its LayerIds property.
I started by taking the code in the post referred to above and extended it to contain a new CLG command to Create a Layer Group. This command lists the available layers for the user to select from, and creates a layer group containing the selected layers.
Here's the updated C# code - pay particular attention to the new CLG command, of course:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.LayerManager;
  6. using System.Collections.Generic;
  7. namespace LayerFilters
  8. {
  9.   public class Commands
  10.   {
  11.     [CommandMethod("LLFS")]
  12.     static public void ListLayerFilters()
  13.     {
  14.       Document doc =
  15.         Application.DocumentManager.MdiActiveDocument;
  16.       Database db = doc.Database;
  17.       Editor ed = doc.Editor;
  18.       // List the nested layer filters
  19.       LayerFilterCollection lfc =
  20.         db.LayerFilters.Root.NestedFilters;
  21.       for (int i = 0; i < lfc.Count; ++i)
  22.       {
  23.         LayerFilter lf = lfc[i];
  24.         ed.WriteMessage(
  25.           "\n{0} - {1} (can{2} be deleted)",
  26.           i + 1,
  27.           lf.Name,
  28.           (lf.AllowDelete ? "" : "not")
  29.         );
  30.       }
  31.     }
  32.     [CommandMethod("CLFS")]
  33.     static public void CreateLayerFilters()
  34.     {
  35.       Document doc =
  36.         Application.DocumentManager.MdiActiveDocument;
  37.       Database db = doc.Database;
  38.       Editor ed = doc.Editor;
  39.       try
  40.       {
  41.         // Get the existing layer filters
  42.         // (we will add to them and set them back)
  43.         LayerFilterTree lft =
  44.           db.LayerFilters;
  45.         LayerFilterCollection lfc =
  46.           lft.Root.NestedFilters;
  47.         // Create three new layer filters
  48.         LayerFilter lf1 = new LayerFilter();
  49.         lf1.Name = "Unlocked Layers";
  50.         lf1.FilterExpression = "LOCKED=="False"";
  51.         LayerFilter lf2 = new LayerFilter();
  52.         lf2.Name = "White Layers";
  53.         lf2.FilterExpression = "COLOR=="7"";
  54.         LayerFilter lf3 = new LayerFilter();
  55.         lf3.Name = "Visible Layers";
  56.         lf3.FilterExpression =
  57.           "OFF=="False" AND FROZEN=="False"";
  58.         // Add them to the collection
  59.         lfc.Add(lf1);
  60.         lfc.Add(lf2);
  61.         lfc.Add(lf3);
  62.         // Set them back on the Database
  63.         db.LayerFilters = lft;
  64.         // List the layer filters, to see the new ones
  65.         ListLayerFilters();
  66.       }
  67.       catch (Exception ex)
  68.       {
  69.         ed.WriteMessage(
  70.           "\nException: {0}",
  71.           ex.Message
  72.         );
  73.       }
  74.     }
  75.     [CommandMethod("CLG")]
  76.     static public void CreateLayerGroup()
  77.     {
  78.       Document doc =
  79.         Application.DocumentManager.MdiActiveDocument;
  80.       Database db = doc.Database;
  81.       Editor ed = doc.Editor;
  82.       // A list of the layers' names & IDs contained
  83.       // in the current database, sorted by layer name
  84.       SortedList<string, ObjectId> ld =
  85.         new SortedList<string, ObjectId>();
  86.       // A list of the selected layers' IDs
  87.       ObjectIdCollection lids =
  88.         new ObjectIdCollection();
  89.       // Start by populating the list of names/IDs
  90.       // from the LayerTable
  91.       Transaction tr =
  92.         db.TransactionManager.StartTransaction();
  93.       using (tr)
  94.       {
  95.         LayerTable lt =
  96.           (LayerTable)tr.GetObject(
  97.             db.LayerTableId,
  98.             OpenMode.ForRead
  99.           );
  100.         foreach(ObjectId lid in lt)
  101.         {
  102.           LayerTableRecord ltr =
  103.             (LayerTableRecord)tr.GetObject(
  104.               lid,
  105.               OpenMode.ForRead
  106.           );
  107.           ld.Add(ltr.Name, lid);
  108.         }
  109.       }
  110.       // Display a numbered list of the available layers
  111.       ed.WriteMessage("\nLayers available for group:");
  112.       int i = 1;
  113.       foreach (KeyValuePair<string,ObjectId> kv in ld)
  114.       {
  115.         ed.WriteMessage(
  116.           "\n{0} - {1}",
  117.           i++,
  118.           kv.Key
  119.         );
  120.       }
  121.       // We will ask the user to select from the list
  122.       PromptIntegerOptions pio =
  123.         new PromptIntegerOptions(
  124.           "\nEnter number of layer to add: "
  125.         );
  126.       pio.LowerLimit = 1;
  127.       pio.UpperLimit = ld.Count;
  128.       pio.AllowNone = true;
  129.       // And will do so in a loop, waiting for
  130.       // Escape or Enter to terminate
  131.       PromptIntegerResult pir;
  132.       do
  133.       {
  134.         // Select one from the list
  135.         pir = ed.GetInteger(pio);
  136.         if (pir.Status == PromptStatus.OK)
  137.         {
  138.           // Get the layer's name
  139.           string ln =
  140.             ld.Keys[pir.Value-1];
  141.           // And then its ID
  142.           ObjectId lid;
  143.           ld.TryGetValue(ln, out lid);
  144.           // Add the layer'd ID to the list, is it's not
  145.           // already on it
  146.           if (lids.Contains(lid))
  147.           {
  148.             ed.WriteMessage(
  149.               "\nLayer "{0}" has already been selected.",
  150.               ln
  151.             );
  152.           }
  153.           else
  154.           {
  155.             lids.Add(lid);
  156.             ed.WriteMessage(
  157.               "\nAdded "{0}" to selected layers.",
  158.               ln
  159.             );
  160.           }
  161.         }
  162.       } while (pir.Status == PromptStatus.OK);
  163.       // Now we've selected our layers, let's create the group
  164.       try
  165.       {
  166.         if (lids.Count > 0)
  167.         {
  168.           // Get the existing layer filters
  169.           // (we will add to them and set them back)
  170.           LayerFilterTree lft =
  171.             db.LayerFilters;
  172.           LayerFilterCollection lfc =
  173.             lft.Root.NestedFilters;
  174.           // Create a new layer group
  175.           LayerGroup lg = new LayerGroup();
  176.           lg.Name = "My Layer Group";
  177.           // Add our layers' IDs to the list
  178.           foreach (ObjectId id in lids)
  179.             lg.LayerIds.Add(id);
  180.           // Add the group to the collection
  181.           lfc.Add(lg);
  182.           // Set them back on the Database
  183.           db.LayerFilters = lft;
  184.           ed.WriteMessage(
  185.             "\n"{0}" group created containing {1} layers.\n",
  186.             lg.Name,
  187.             lids.Count
  188.           );
  189.           // List the layer filters, to see the new group
  190.           ListLayerFilters();
  191.         }
  192.       }
  193.       catch (Exception ex)
  194.       {
  195.         ed.WriteMessage(
  196.           "\nException: {0}",
  197.           ex.Message
  198.         );
  199.       }
  200.     }
  201.     [CommandMethod("DLF")]
  202.     static public void DeleteLayerFilter()
  203.     {
  204.       Document doc =
  205.         Application.DocumentManager.MdiActiveDocument;
  206.       Database db = doc.Database;
  207.       Editor ed = doc.Editor;
  208.       ListLayerFilters();
  209.       try
  210.       {
  211.         // Get the existing layer filters
  212.         // (we will add to them and set them back)
  213.         LayerFilterTree lft =
  214.           db.LayerFilters;
  215.         LayerFilterCollection lfc =
  216.           lft.Root.NestedFilters;
  217.         // Prompt for the index of the filter to delete
  218.         PromptIntegerOptions pio =
  219.           new PromptIntegerOptions(
  220.           "\n\nEnter index of filter to delete"
  221.           );
  222.         pio.LowerLimit = 1;
  223.         pio.UpperLimit = lfc.Count;
  224.         PromptIntegerResult pir =
  225.           ed.GetInteger(pio);
  226.         // Get the selected filter
  227.         LayerFilter lf = lfc[pir.Value - 1];
  228.         // If it's possible to delete it, do so
  229.         if (!lf.AllowDelete)
  230.         {
  231.           ed.WriteMessage(
  232.             "\nLayer filter cannot be deleted."
  233.           );
  234.         }
  235.         else
  236.         {
  237.           lfc.Remove(lf);
  238.           db.LayerFilters = lft;
  239.           ListLayerFilters();
  240.         }
  241.       }
  242.       catch(Exception ex)
  243.       {
  244.         ed.WriteMessage(
  245.           "\nException: {0}",
  246.           ex.Message
  247.         );
  248.       }
  249.     }
  250.   }
  251. }
Here's what happens when we run the code on a drawing containing a number of layers (all of which have the default properties for new layers):
  1. Command: CLG
  2. Layers available for group:
  3. 1 - 0
  4. 2 - Layer1
  5. 3 - Layer2
  6. 4 - Layer3
  7. 5 - Layer4
  8. 6 - Layer5
  9. 7 - Layer6
  10. 8 - Layer7
  11. 9 - Layer8
  12. 10 - Layer9
  13. 11 - Test1
  14. 12 - Test2
  15. 13 - Test3
  16. 14 - Test4
  17. 15 - Test5
  18. Enter number of layer to add: 2
  19. Added "Layer1" to selected layers.
  20. Enter number of layer to add: 4
  21. Added "Layer3" to selected layers.
  22. Enter number of layer to add: 6
  23. Added "Layer5" to selected layers.
  24. Enter number of layer to add: 8
  25. Added "Layer7" to selected layers.
  26. Enter number of layer to add: 11
  27. Added "Test1" to selected layers.
  28. Enter number of layer to add: 15
  29. Added "Test5" to selected layers.
  30. Enter number of layer to add:
  31. "My Layer Group" group created containing 6 layers.
  32. 1 - My Layer Group (can be deleted)
  33. 2 - All Used Layers (cannot be deleted)
  34. 3 - Unreconciled New Layers (cannot be deleted)
  35. 4 - Viewport Overrides (cannot be deleted)
复制代码
I've hard-coded the name of the new layer group to be "My Layer Group". It's a trivial exercise to modify the code to ask the user to enter a name, each time.
Here is how our new group looks in AutoCAD's Layer dialog:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-6-7 16:24 | 显示全部楼层
十三、向.NetApi开放AutoCAD中的属性面板的功能
OPM & 例子


March 13, 2009
Exposing AutoCAD's Properties Palette functionality to .NET - Part 1
A huge thanks to Cyrille Fauvel, who manages DevTech's Media & Entertainment team but still finds the time to dip into the odd deeply-technical, AutoCAD-related issue. Cyrille provided the original article on this topic late last year, but it's taken me time to get around to editing and publishing it. A quick tip... if you're not interested in the technical details of how Cyrille has exposed the various Properties Palette interfaces to .NET, you can safely skip this post and join us again when we go ahead and make use of the implementation to add dynamic properties to core AutoCAD objects using C#. Most people won't want to know all the details included in this post, but are just interested in the end results: next time I'll be providing a pre-built ObjectARX module, along with full source-code, that can be loaded into AutoCAD 2007-2009 to enable the use of the Properties Palette from .NET.
AutoCAD's Properties Palette - once known as the Object Properties Manager (OPM) - is a very handy way to display properties inside your application, whether those properties are associated with individual objects or with the application itself. The Properties Palette uses COM to communicate with the object(s) in question, and has always required the use of C++ to expose particular interfaces that control the display of the properties in the palette, so its functionality has not been available to developers using managed .NET languages such as C# and VB.NET.
There is some portion of the Properties Palette functionality exposed via the Autodesk.AutoCAD.Windows.ToolPalette namespace, such as the IAcPiPropertyDisplay interface allowing objects and commands to customize the display of properties in the property inspector window, but this is far from complete. This post looks at exposing more of the standard Properties Palette functionality to .NET languages, and next time we'll look at some specific examples of using it from C#.
Our first step is to expose some "OPM" interfaces to .NET. There are two ways to do this: we can expose them from our .NET application sung standard C# or VB.NET code, or we can use a small ObjectARX module to implement them.
Well, we have to use ObjectARX to access some OPM functionality that is currently only exposed via C++, so we're going to go down that path. What we're going to do is expose the interface(s) we want from our ObjectARX module and marshal the various parameters to be useable from .NET. Simple! :-)
To get started, we're going to look at AutoCAD's IPropertyManager2 interface, which is contained in dynprops.h on the ObjectARX SDK:
  1. //--------------------------
  2. // IPropertyManager2 interface
  3. // This is the main property manager class. Use this to add your
  4. // property classes for a given type of IUnknown object.
  5. // You can get this interface using
  6. // CreateOPMIUnknownProtocol(ppUnk)->GetPropertyManager2().
  7. //--------------------------
  8. // {FABC1C70-1044-4aa0-BF8D-91FFF9052715}
  9. DEFINE_GUID(IID_IPropertyManager2, 0xfabc1c70, 0x1044, 0x4aa0, 0xbf, 0x8d, 0x91, 0xff, 0xf9, 0x5, 0x27, 0x15);
  10. interface DECLSPEC_UUID("FABC1C70-1044-4aa0-BF8D-91FFF9052715")
  11. IPropertyManager2 : public IUnknown
  12. {
  13.     BEGIN_INTERFACE
  14.     // *** IUnknown methods ****
  15.     STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
  16.     STDMETHOD_(ULONG, AddRef)(THIS) PURE;
  17.     STDMETHOD_(ULONG, Release)(THIS) PURE;
  18.     // *** IPropertyManager2 methods ***
  19.     STDMETHOD(AddProperty)(THIS_ IUnknown FAR* pDynPropObj) PURE;
  20.     STDMETHOD(RemoveProperty)(THIS_ IUnknown FAR* pDynPropObj) PURE;
  21.     STDMETHOD(GetDynamicProperty)(THIS_ /*[in]*/LONG index,
  22.                                     /*[out]*/IUnknown ** pDynPropObj) PURE;
  23.     STDMETHOD(GetDynamicPropertyByName)(THIS_ /*[in]*/BSTR propName,
  24.                                           /*[out]*/IUnknown ** pDynPropObj) PURE;
  25.     STDMETHOD(GetDynamicPropertyCountEx)(THIS_ /*[out]*/LONG* count) PURE;
  26.     //For COM Wrappers to generate dynamic property typeinfo
  27.     STDMETHOD(GetDynamicClassInfo)(THIS_ /*[in]*/IUnknown* pObj,
  28.                                         /*[out]*/ITypeInfo** pptiDynamic,
  29.                                         /*[out]*/DWORD* dwCookie) PURE;
  30. };
Now in our ObjectARX application we're going to expose the IPropertyManager2 interface to .NET.
At line 41 in the IPropertyManager2.h file in the provided project [which will actually be provided with the final post of the series], we declare the interface using the same Global Unique Identifier (GUID):
[InteropServices::Guid("FABC1C70-1044-4aa0-BF8D-91FFF9052715")]
Then we need to indicate that our interface is derived from the standard, root COM interface, IUnknown:
[InteropServices::InterfaceTypeAttribute(InteropServices::ComInterfaceType::InterfaceIsIUnknown)]
We make it visible to COM, of course:
[InteropServices::ComVisible(true)]
Now for the interface itself. We declare that it's a "public interface class" along with its various members, specifying how to marshal all the types. We're not going to return values from our methods (which usually indicate success or failure): we'll leave it to the .NET code that implements the interfaces (which we'll see in the final post of the series) to throw exceptions instead.
Here's the full class declaration, edited for display on this blog:
  1. namespace Autodesk
  2. {
  3.   namespace AutoCAD
  4.   {
  5.     namespace Windows
  6.     {
  7.       namespace OPM
  8.       {
  9.         [InteropServices::Guid(
  10.           "FABC1C70-1044-4aa0-BF8D-91FFF9052715"
  11.         )]
  12.         [InteropServices::InterfaceTypeAttribute(
  13.           InteropServices::ComInterfaceType::InterfaceIsIUnknown
  14.         )]
  15.         [InteropServices::ComVisible(true)]
  16.         public interface class IPropertyManager2
  17.         {
  18.           void AddProperty(
  19.             [InteropServices::In,
  20.               InteropServices::MarshalAs(
  21.                 InteropServices::UnmanagedType::IUnknown
  22.               )
  23.             ] Object^ pDynPropObj
  24.           );
  25.           void RemoveProperty(
  26.             [InteropServices::In,
  27.               InteropServices::MarshalAs(
  28.                 InteropServices::UnmanagedType::IUnknown
  29.               )
  30.             ] Object^ pDynPropObj
  31.           );
  32.           void GetDynamicProperty(
  33.             [InteropServices::In] long index,
  34.             [InteropServices::Out,
  35.               InteropServices::MarshalAs(
  36.                 InteropServices::UnmanagedType::IUnknown
  37.               )
  38.             ] interior_ptr<Object^> value
  39.           );
  40.           void GetDynamicPropertyByName(
  41.             [InteropServices::In,
  42.               InteropServices::MarshalAs(
  43.                 InteropServices::UnmanagedType::BStr
  44.               )
  45.             ] System::String^ name,
  46.             [InteropServices::Out,
  47.               InteropServices::MarshalAs(
  48.                 InteropServices::UnmanagedType::IUnknown
  49.               )
  50.             ] interior_ptr<Object^> value
  51.           );
  52.           void GetDynamicPropertyCountEx(
  53.             [InteropServices::Out] long* count
  54.             );
  55.           void GetDynamicClassInfo(
  56.             [InteropServices::In,
  57.               InteropServices::MarshalAs(
  58.                 InteropServices::UnmanagedType::IUnknown
  59.               )
  60.             ] Object^ pDynPropObj,
  61.             [InteropServices::Out,
  62.               InteropServices::MarshalAs(
  63.                 /*InteropServices::UnmanagedType::ITypeInfo*/
  64.                 InteropServices::UnmanagedType::IUnknown
  65.               )
  66.             ] interior_ptr<Object^> typeInfo,
  67.             [InteropServices::Out] ulong* dwCookie
  68.           );
  69.         };
  70.       }
  71.     }
  72.   }
  73. }
This one class is really at the core of our effort to expose the OPM to .NET. To let us access and use this class, we're going to expose a "property extension factory". We might have opted to P/Invoke an ObjectARX API for this, but it's ultimately cleaner to implement this from C++ and expose it via a managed wrapper.
So how do we know we need a property extension factory? Typically to access the IPropertyManager2 instance from ObjectARX we use the GET_OPMPROPERTY_MANAGER() macro. From dynprops.h, we know that this macro expands into GET_OPMEXTENSION_CREATE_PROTOCOL()->CreateOPMObjectProtocol(pAcRxClass)->GetPropertyManager() and the GET_OPMEXTENSION_CREATE_PROTOCOL() macro expands, in turn, into OPMPropertyExtensionFactory::cast(AcDbDatabase::desc()->queryX(OPMPropertyExtensionFactory::desc())). This is how we know we need to expose the OPMPropertyExtensionFactory class.
So let's take a look at the declaration in the ObjectARX SDK of OPMPropertyExtensionFactory, once again from dynprops.h:
  1. //--------------------------
  2. // OPMPropertyExtension interface
  3. // This class is implemented by AutoCAD and available through
  4. // GET_OPMEXTENSION_CREATE_PROTOCOL. You can add property classes
  5. // by calling GET_OPMPROPERTY_MANAGER for a particular AcRxClass
  6. // to get the property manager for that class.
  7. // You can also enumerate the dynamic properties which have
  8. // been added to that class as well as its base class(es) via
  9. // GetPropertyCount and GetPropertyClassArray
  10. //--------------------------
  11. class OPMPropertyExtensionFactory: public AcRxObject
  12. {
  13. public:
  14.     ACRX_DECLARE_MEMBERS(OPMPropertyExtensionFactory);
  15.     virtual ~OPMPropertyExtensionFactory(){}
  16.     //Retrieves the OPMPropertyExtension for the specified class, if the
  17.     //extension has not been added before, it creates it. Note: the implementation
  18.     //of this class manages the lifetime of OPMPropertyExtension, as such you don't
  19.     //need to delete them.
  20.     virtual OPMPropertyExtension* CreateOPMObjectProtocol(AcRxClass* pClass,
  21.                                                             LONG lReserved = NULL) = 0;
  22. ...
  23. }
复制代码
To expose this class through .NET we're going to use the Autodesk::AutoCAD::Runtime::Wrapper attribute class to tell AutoCAD to manage this class as an internal class object wrapper. We then derive our wrapper class from RXObject (as the base class of the corresponding ObjectARX class is AcRxObject), and implement a constructor, a GetImpObj() method to provide access to the unmanaged object and our factory function itself, CreateOPMObjectProtocol(). There's no need for a destructor as this is handled by the wrapper.
Here's the complete class declaration from OPMPropertyExtensionFactory.h in the provided sample:
  1. namespace Autodesk
  2. {
  3.   namespace AutoCAD
  4.   {
  5.     namespace Windows
  6.     {
  7.       namespace OPM
  8.       {
  9.         [Autodesk::AutoCAD::Runtime::Wrapper(
  10.           "OPMPropertyExtensionFactory"
  11.         )]
  12.         public ref class AcMgdOPMPropertyExtensionFactory
  13.           : public RXObject
  14.         {
  15.         public protected:
  16.           AcMgdOPMPropertyExtensionFactory(
  17.             System::IntPtr unmanagedPointer,
  18.             bool bAutoDelete
  19.           )
  20.             : RXObject(unmanagedPointer, bAutoDelete) {}
  21.         internal:
  22.           //- Returns the unmanaged ARX Object
  23.           inline OPMPropertyExtensionFactory* GetImpObj()
  24.           {
  25.             return(
  26.               static_cast<OPMPropertyExtensionFactory *>(
  27.                 UnmanagedObject.ToPointer()
  28.               )
  29.             );
  30.           }
  31.         public:
  32.           virtual AcMgdOPMPropertyExtension^ CreateOPMObjectProtocol(
  33.             RXClass^ runtimeClass,
  34.             long lReserved
  35.           );
  36.         } ;
  37.       }
  38.     }
  39.   }
  40. }
There is one last class that needs to be exposed: OPMPropertyExtension, which is what is created by the OPMPropertyExtensionFactory (logically enough). We'll use the same technique as for the OPMPropertyExtensionFactory.
Here's the class declaration from OPMPropertyExtension.h:
  1. namespace Autodesk
  2. {
  3.   namespace AutoCAD
  4.   {
  5.     namespace Windows
  6.     {
  7.       namespace OPM
  8.       {
  9.         [Autodesk::AutoCAD::Runtime::Wrapper(
  10.           "OPMPropertyExtension"
  11.         )]
  12.         public ref class AcMgdOPMPropertyExtension : public RXObject
  13.         {
  14.         public protected:
  15.           AcMgdOPMPropertyExtension(
  16.             System::IntPtr unmanagedPointer,
  17.             bool bAutoDelete
  18.           )
  19.             : RXObject (unmanagedPointer, bAutoDelete) {}
  20.         internal:
  21.           //- Returns the unmanaged ARX Object
  22.           inline OPMPropertyExtension* GetImpObj()
  23.           {
  24.             return (
  25.               static_cast<OPMPropertyExtension *>(
  26.                 UnmanagedObject.ToPointer()
  27.               )
  28.             );
  29.           }
  30.         public:
  31.           virtual Object^ GetPropertyManager();
  32.           virtual void SetPropertyManager(Object^ pPropManager);
  33.         } ;
  34.       }
  35.     }
  36.   }
  37. }
Those are all the declarations (yes, in C++ you need declare your classes separately from their definitions... yawn... :-) so let's go ahead and implement the wrapper classes.
First the OPMPropertyExtensionFactory class (from OPMPropertyExtensionFactory.cpp), with it's main method to create and return a new OPMPropertyExtension:
  1. namespace Autodesk
  2. {
  3.   namespace AutoCAD
  4.   {
  5.     namespace Windows
  6.     {
  7.       namespace OPM
  8.       {
  9.         AcMgdOPMPropertyExtension^
  10.           AcMgdOPMPropertyExtensionFactory::CreateOPMObjectProtocol(
  11.             RXClass^ runtimeClass, long lReserved
  12.           )
  13.         {
  14.           return (
  15.             gcnew AcMgdOPMPropertyExtension(
  16.               System::IntPtr(
  17.                 GetImpObj()->CreateOPMObjectProtocol(
  18.                   static_cast<AcRxClass*>(
  19.                     //runtimeClass->GetImpObj()
  20.                     runtimeClass->UnmanagedObject.ToPointer()
  21.                   ),
  22.                   lReserved
  23.                 )
  24.               ),
  25.               false
  26.             )
  27.           );
  28.         }
  29.       }
  30.     }
  31.   }
  32. }
Here's the implementation of the OPMPropertyExtension class (from OPMPropertyExtension.cpp), which gets us access to the OPMPropertyManager:
  1. namespace Autodesk
  2. {
  3.   namespace AutoCAD
  4.   {
  5.     namespace Windows
  6.     {
  7.       namespace OPM
  8.       {
  9.         Object^ AcMgdOPMPropertyExtension::GetPropertyManager()
  10.         {
  11.           IUnknown *pUnk =
  12.             GetImpObj()->GetPropertyManager();
  13.           return (
  14.             System::Runtime::InteropServices::Marshal::
  15.             GetObjectForIUnknown(System::IntPtr(pUnk))
  16.           );
  17.         }
  18.         void AcMgdOPMPropertyExtension::SetPropertyManager(
  19.           Object^ pPropManager
  20.         )
  21.         {
  22.           IPropertyManager *pPropMgr =
  23.             reinterpret_cast<IPropertyManager *>(
  24.               System::Runtime::InteropServices::Marshal::
  25.               GetIUnknownForObject(pPropManager).ToPointer()
  26.             );
  27.           GetImpObj()->SetPropertyManager(pPropMgr);
  28.         }
  29.       }
  30.     }
  31.   }
  32. }
To finish off our implementation, we're going to re-implement the two handy macros we have in unmanaged C++ (i.e. ObjectARX), to give us the same capabilities when in a managed environment.
Here's our implementation from xOPM.cpp:
  1. namespace Autodesk
  2. {
  3.   namespace AutoCAD
  4.   {
  5.     namespace Windows
  6.     {
  7.       namespace OPM
  8.       {
  9.         AcMgdOPMPropertyExtensionFactory^
  10.           xOPM::xGET_OPMEXTENSION_CREATE_PROTOCOL()
  11.         {
  12.           Dictionary^ classDict =
  13.             SystemObjects::ClassDictionary;
  14.           RXClass^ opmFactoryClass =
  15.             (RXClass^)classDict->At("OPMPropertyExtensionFactory");
  16.           RXClass^ dbClass =
  17.             (RXClass^)classDict->At("AcDbDatabase");
  18.           return(
  19.             gcnew AcMgdOPMPropertyExtensionFactory(
  20.               dbClass->QueryX (opmFactoryClass),
  21.               false
  22.             )
  23.           );
  24.         }
  25.         Object^
  26.           xOPM::xGET_OPMPROPERTY_MANAGER(RXClass^ pAcRxClass)
  27.         {
  28.           AcMgdOPMPropertyExtensionFactory^ opmFactory =
  29.             xOPM::xGET_OPMEXTENSION_CREATE_PROTOCOL();
  30.           return(
  31.             opmFactory->
  32.               CreateOPMObjectProtocol(pAcRxClass, 0)->
  33.                 GetPropertyManager());
  34.         }
  35.       }
  36.     }
  37.   }
  38. }
There's just one more interface that needs exposing to .NET, and that's IDynamicProperty2. We're going to expose this in much the same way as we did IPropertyManager2.
Here's the original declaration from dynprops.h:
  1. //--------------------------
  2. // IDynamicProperty2 interface
  3. // Implement this class to create dynamic properties for the PropertyPalette
  4. // it defines the base set of property attributes as well as
  5. // the name/type/data tuples.
  6. //--------------------------
  7. // {9CAF41C2-CA86-4ffb-B05A-AC43C424D076}
  8. DEFINE_GUID(IID_IDynamicProperty2, 0x9caf41c2, 0xca86, 0x4ffb, 0xb0, 0x5a, 0xac, 0x43, 0xc4, 0x24, 0xd0, 0x76);
  9. interface DECLSPEC_UUID("9CAF41C2-CA86-4ffb-B05A-AC43C424D076")
  10. IDynamicProperty2 : public IUnknown
  11. {
  12.     BEGIN_INTERFACE
  13.     // *** IUnknown methods ****
  14.     STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
  15.     STDMETHOD_(ULONG, AddRef)(THIS) PURE;
  16.     STDMETHOD_(ULONG, Release)(THIS) PURE;
  17.     // *** IDynamicProperty2 methods ***
  18.     //Unique property ID
  19.     STDMETHOD(GetGUID)(THIS_ /*[out]*/GUID* propGUID) PURE;
  20.     // Property display name
  21.     STDMETHOD(GetDisplayName)(THIS_ /*[out]*/BSTR* bstrName) PURE;
  22.     // Show/Hide property in the OPM, for this object instance
  23.     STDMETHOD(IsPropertyEnabled)(THIS_ /*[in]*/IUnknown *pUnk,
  24.                                       /*[out]*/BOOL* pbEnabled) PURE;
  25.     // Is property showing but disabled
  26.     STDMETHOD(IsPropertyReadOnly)(THIS_ /*[out]*/BOOL* pbReadonly) PURE;
  27.     // Get the property description string
  28.     STDMETHOD(GetDescription)(THIS_ /*[out]*/BSTR* bstrName) PURE;
  29.     // *** Basic property value information ***
  30.     // OPM will typically display these in an edit field
  31.     // optional: meta data representing property type name, ex. ACAD_ANGLE
  32.     STDMETHOD(GetCurrentValueName)(THIS_ /*[out]*/BSTR* pbstrName) PURE;
  33.     // What is the property type, ex. VT_R8
  34.     STDMETHOD(GetCurrentValueType)(THIS_ /*[out]*/VARTYPE* pVarType) PURE;
  35.     // Get the property value, passes the specific object we need the property
  36.     // value for.
  37.     STDMETHOD(GetCurrentValueData)(THIS_ /*in*/IUnknown *pUnk,
  38.                                       /*[out]*/VARIANT* pvarData) PURE;
  39.     // Set the property value, passes the specific object we want to set the
  40.     // property value for
  41.     STDMETHOD(SetCurrentValueData)(THIS_ /*[in]*/IUnknown *pUnk,
  42.                                         /*[in]*/const VARIANT varData) PURE;
  43.     //*** Notifications ***
  44.     //OPM passes its implementation of IDynamicPropertyNotify, you
  45.     //cache it and call it to inform OPM your property has changed
  46.     STDMETHOD(Connect)(THIS_ /*[in]*/IDynamicPropertyNotify2* pSink) PURE;
  47.     STDMETHOD(Disconnect)(THIS_ ) PURE;
  48. };
复制代码
And here's our own exposure of this interface to .NET (from IDynamicProperty2.h):
  1. namespace Autodesk
  2. {
  3.   namespace AutoCAD
  4.   {
  5.     namespace Windows
  6.     {
  7.       namespace OPM
  8.       {
  9.         [InteropServices::Guid(
  10.           "9CAF41C2-CA86-4ffb-B05A-AC43C424D076"
  11.         )]
  12.         [InteropServices::InterfaceTypeAttribute(
  13.           InteropServices::ComInterfaceType::InterfaceIsIUnknown
  14.         )]
  15.         [InteropServices::ComVisible(true)]
  16.         public interface class IDynamicProperty2
  17.         {
  18.           void GetGUID(
  19.             [InteropServices::Out] System::Guid% propGUID
  20.           );
  21.           void GetDisplayName(
  22.             [InteropServices::Out,
  23.             InteropServices::MarshalAs(
  24.               InteropServices::UnmanagedType::BStr
  25.             )
  26.             ] interior_ptr<System::String^> name);
  27.           void IsPropertyEnabled(
  28.             [InteropServices::In,
  29.             InteropServices::MarshalAs(
  30.               InteropServices::UnmanagedType::IUnknown
  31.             )
  32.             ] Object^ pUnk,
  33.             [InteropServices::Out] System::Int32% bEnabled
  34.           );
  35.           void IsPropertyReadOnly(
  36.             [InteropServices::Out] System::Int32% bReadonly
  37.           );
  38.           void GetDescription(
  39.             [InteropServices::Out,
  40.             InteropServices::MarshalAs(
  41.               InteropServices::UnmanagedType::BStr
  42.             )
  43.             ] interior_ptr<System::String^> description
  44.           );
  45.           void GetCurrentValueName(
  46.             [InteropServices::Out,
  47.             InteropServices::MarshalAs(
  48.               InteropServices::UnmanagedType::BStr
  49.             )
  50.             ] interior_ptr<System::String^> name
  51.           );
  52.           void GetCurrentValueType(
  53.             [InteropServices::Out] ushort% pVarType
  54.           );
  55.           void GetCurrentValueData(
  56.             [InteropServices::In,
  57.             InteropServices::MarshalAs(
  58.               InteropServices::UnmanagedType::IUnknown
  59.             )
  60.             ] Object^ pUnk,
  61.             [InteropServices::In,
  62.             InteropServices::Out,
  63.             InteropServices::MarshalAs(
  64.               InteropServices::UnmanagedType::Struct
  65.             )
  66.             ] interior_ptr<Object^> varData
  67.           );
  68.           void SetCurrentValueData(
  69.             [InteropServices::In,
  70.             InteropServices::MarshalAs(
  71.               InteropServices::UnmanagedType::IUnknown
  72.             )
  73.             ] Object^ pUnk,
  74.             [InteropServices::In,
  75.             InteropServices::MarshalAs(
  76.               InteropServices::UnmanagedType::Struct
  77.             )
  78.             ] Object^ varData
  79.           );
  80.           void Connect(
  81.             [InteropServices::In,
  82.             InteropServices::MarshalAs(
  83.               /*IDynamicPropertyNotify2*/
  84.               InteropServices::UnmanagedType::IUnknown
  85.             )
  86.             ] Object^ pSink
  87.           );
  88.           void Disconnect();
  89.         };
  90.         [InteropServices::Guid(
  91.           "975112B5-5403-4197-AFB8-90C6CA73B9E1"
  92.         )]
  93.         [InteropServices::InterfaceTypeAttribute(
  94.           InteropServices::ComInterfaceType::InterfaceIsIUnknown
  95.         )]
  96.         [InteropServices::ComVisible(true)]
  97.         public interface class IDynamicPropertyNotify2
  98.         {
  99.           void OnChanged(
  100.             [InteropServices::In,
  101.             InteropServices::MarshalAs(
  102.               InteropServices::UnmanagedType::IUnknown
  103.             )
  104.             ] Object^ pDynamicProperty
  105.           );
  106.           void GetCurrentSelectionSet(
  107.             [InteropServices::In,
  108.             InteropServices::Out,
  109.             InteropServices::MarshalAs(
  110.               InteropServices::UnmanagedType::Struct
  111.             )
  112.             ] interior_ptr<Object^> pSelection
  113.           );
  114.         };
  115.       }
  116.     }
  117.   }
  118. }
OK, that's it for the hardcore technical details on exposing AutoCAD's Properties Palette to .NET. In the next post we'll start the real fun with some real examples of using these interfaces from C# (and you can trust me when I say that's going to be a whole lot simpler than this post, honestly! :-).

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-6-7 16:27 | 显示全部楼层
March 16, 2009
Exposing AutoCAD's Properties Palette functionality to .NET - Part 2
In the last post we looked at the code behind an ObjectARX module exposing AutoCAD's Properties Palette for use from managed .NET languages. Thanks again to Cyrille Fauvel for providing this implementation. In this post we're going to move right onto using this implementation from C#.
First things first: if you didn't understand much of what was said in the previous post in this series, Don't Panic! (Yes, that's a quick reference to  The Hitchhiker's Guide to the Galaxy.) The actual implementation details aren't particularly important - you only really need to understand them if you want to expose additional interfaces to .NET in the same way (such as IFilterableProperty, the interface discussed in this previous post and something I expect to extend the application to handle, at some point).
Here is the project that contains both the ObjectARX module implementing exposing these interfaces and the sample we're showing today. You simply have to make sure the .NET module can find our asdkOPMNetExt.dll module (ideally both this and your .NET module should be located under AutoCAD's program folder).
Then you can simply implement code, as in the below sample, which loads and makes use of interfaces exposed by this assembly.
Here’s the C# code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.Runtime;
  3. using Autodesk.AutoCAD.Windows.OPM;
  4. using System;
  5. using System.Reflection;
  6. using System.Runtime.InteropServices;
  7. namespace OPMNetSample
  8. {
  9.   #region Our Custom Property
  10.   [
  11.     Guid("F60AE3DA-0373-4d24-82D2-B2646517ABCB"),
  12.     ProgId("OPMNetSample.CustomProperty.1"),
  13.     // No class interface is generated for this class and
  14.     // no interface is marked as the default.
  15.     // Users are expected to expose functionality through
  16.     // interfaces that will be explicitly exposed by the object
  17.     // This means the object can only expose interfaces we define
  18.     ClassInterface(ClassInterfaceType.None),
  19.     // Set the default COM interface that will be used for
  20.     // Automation. Languages like: C#, C++ and VB allow to
  21.     //query for interface's we're interested in but Automation
  22.     // only aware languages like javascript do not allow to
  23.     // query interface(s) and create only the default one
  24.     ComDefaultInterface(typeof(IDynamicProperty2)),
  25.     ComVisible(true)
  26.   ]
  27.   public class CustomProp : IDynamicProperty2
  28.   {
  29.     private IDynamicPropertyNotify2 m_pSink = null;
  30.     // Unique property ID
  31.     public void GetGUID(out Guid propGUID)
  32.     {
  33.       propGUID =
  34.         new Guid("F60AE3DA-0373-4d24-82D2-B2646517ABCB");
  35.     }
  36.     // Property display name
  37.     public void GetDisplayName(out string szName)
  38.     {
  39.       szName = "My integer property";
  40.     }
  41.     // Show/Hide property in the OPM, for this object instance
  42.     public void IsPropertyEnabled(object pUnk, out int bEnabled)
  43.     {
  44.       bEnabled = 1;
  45.     }
  46.     // Is property showing but disabled
  47.     public void IsPropertyReadOnly(out int bReadonly)
  48.     {
  49.       bReadonly = 0;
  50.     }
  51.     // Get the property description string
  52.     public void GetDescription(out string szName)
  53.     {
  54.       szName =
  55.         "This property is an integer";
  56.     }
  57.     // OPM will typically display these in an edit field
  58.     // optional: meta data representing property type name,
  59.     // ex. ACAD_ANGLE
  60.     public void GetCurrentValueName(out string szName)
  61.     {
  62.       throw new System.NotImplementedException();
  63.     }
  64.     // What is the property type, ex. VT_R8
  65.     public void GetCurrentValueType(out ushort varType)
  66.     {
  67.       // The Property Inspector supports the following data
  68.       // types for dynamic properties:
  69.       // VT_I2, VT_I4, VT_R4, VT_R8,VT_BSTR, VT_BOOL
  70.       // and VT_USERDEFINED.
  71.       varType = 3; // VT_I4
  72.     }
  73.     // Get the property value, passes the specific object
  74.     // we need the property value for.
  75.     public void GetCurrentValueData(object pUnk, ref object pVarData)
  76.     {
  77.       // TODO: Get the value and return it to AutoCAD
  78.       // Because we said the value type was a 32b int (VT_I4)
  79.       pVarData = (int)4;
  80.     }
  81.     // Set the property value, passes the specific object we
  82.     // want to set the property value for
  83.     public void SetCurrentValueData(object pUnk, object varData)
  84.     {
  85.       // TODO: Save the value returned to you
  86.       // Because we said the value type was a 32b int (VT_I4)
  87.       int myVal = (int)varData;
  88.     }
  89.     // OPM passes its implementation of IDynamicPropertyNotify, you
  90.     // cache it and call it to inform OPM your property has changed
  91.     public void Connect(object pSink)
  92.     {
  93.       m_pSink = (IDynamicPropertyNotify2)pSink;
  94.     }
  95.     public void Disconnect() {
  96.       m_pSink = null;
  97.     }
  98.   }
  99.   #endregion
  100.   #region Application Entry Point
  101.   public class MyEntryPoint : IExtensionApplication
  102.   {
  103.     protected internal CustomProp custProp = null;
  104.     public void Initialize()
  105.     {
  106.       Assembly.LoadFrom("asdkOPMNetExt.dll");
  107.       // Add the Dynamic Property
  108.       Dictionary classDict = SystemObjects.ClassDictionary;
  109.       RXClass lineDesc = (RXClass)classDict.At("AcDbLine");
  110.       IPropertyManager2 pPropMan =
  111.         (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(lineDesc);
  112.       custProp = new CustomProp();
  113.       pPropMan.AddProperty((object)custProp);
  114.     }
  115.     public void Terminate()
  116.     {
  117.       // Remove the Dynamic Property
  118.       Dictionary classDict = SystemObjects.ClassDictionary;
  119.       RXClass lineDesc = (RXClass)classDict.At("AcDbLine");
  120.       IPropertyManager2 pPropMan =
  121.         (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(lineDesc);
  122.       pPropMan.RemoveProperty((object)custProp);
  123.       custProp = null;
  124.     }
  125.   }
  126.   #endregion
  127. }
A few comments on what this code does…
It defines a class for our custom dynamic property (CustomProp), for which we need a unique GUID (and yes, by definition GUIDs are unique, but if you use the same one twice it ceases to be :-), and implement various callbacks to indicate the name, type, description and writeability of the property, as well as methods to get and set the property value. For this example our property is called “My integer property”, and – guess what? – it’s an integer, and has been hardcoded to have the value 4. We’re not actually storing the data being exposed via this property, but in a real-world application you would probably store it either as XData attached to the object, inside an XRecord in the object’s extension dictionary or in an external database of some kind.
In the rest of the code we’re defining functions that are called when the module is loaded and when AutoCAD terminates (see this previous post for more information on this mechanism). We use the Initialize() callback to load our mixed-mode module for which we presented the code in the last post (asdkOPMNetExt.dll) and then go ahead and instantiate our property and attach it to line objects.
When we build and load the sample, making sure the Properties Palette is visible (using the PROPS command or by double-clicking on the line) and selecting a line we’ve just drawn, we should see our dynamic property appear, as shown in this image.
If you don’t see the property appear, the application is probably having trouble loading asdkOPMNetExt.dll: as mentioned earlier I have placed both this and the sample application in the root folder of my AutoCAD installation. If you’re not sure whether the module is loaded properly, you can step through the Initialize() function in the debugger or add a simple command to your code which will obviously only work if your application has been loaded (the Assembly.LoadFrom() call will throw an exception if it doesn’t find the module, and if an exception is thrown from Initialize() the application will not be loaded, as described in this previous post).
For the sake of simplicity I’m going to leave this basic sample as having just one, hardwired property: hopefully it’s obvious how the application could be extended to handle more properties and to store these properties with their objects (if not, let me know by posting a comment and I’ll put together a more extensive example when I get the chance).

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-8-18 11:30 | 显示全部楼层

Kean专题(14)—Dynamic_Blocks

一、访问动态块属性
March 04, 2009
Accessing the properties of a dynamic AutoCAD block using .NET
This is one of those funny scenarios... I was just thinking about what to do for my next post - whether to dive into some new features of AutoCAD 2010 (which I will do soon, I promise! :-) or whether to choose something from my ever-increasing to-do list, when I received two emails.
One was from our old friend Fernando Malard, suggesting a topic for a blog post, and the other was from Philippe Leefsma, a member of our DevTech team in Europe, in response to an ADN members question. It provided some code that could eventually form the basis for a response to Fernando's question. Coincidence? Maybe. Am I one to ignore serendipity at work (or to look a gift horse in the mouth)? No!
So, here's Fernando's question:
    Just would suggest a new topic for your Blog. An article explaining how to insert a dynamic block and modifying its dynamic properties like point, rotation, dimension. This is something I’m currently working on .NET and I think it would be a very interesting topic to evolve.
Philippe's code is a little different - it shows how to retrieve and display the "dynamic" properties of a dynamic block read in from an external file - but it was a great start for my first step towards Fernando's goal, which was simply to access the dynamic properties for a block selected by the user.
Here's the C# code I based on Philippe's:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Geometry;
  5. using Autodesk.AutoCAD.Runtime;
  6. namespace DynamicBlocks
  7. {
  8.   public class Commands
  9.   {
  10.     [CommandMethod("DBP")]
  11.     static public void DynamicBlockProps()
  12.     {
  13.       Document doc =
  14.         Application.DocumentManager.MdiActiveDocument;
  15.       Database db = doc.Database;
  16.       Editor ed = doc.Editor;
  17.       PromptStringOptions pso =
  18.         new PromptStringOptions(
  19.           "\nEnter dynamic block name or enter to select: "
  20.         );
  21.       pso.AllowSpaces = true;
  22.       PromptResult pr = ed.GetString(pso);
  23.       if (pr.Status != PromptStatus.OK)
  24.         return;
  25.       Transaction tr =
  26.         db.TransactionManager.StartTransaction();
  27.       using (tr)
  28.       {
  29.         BlockReference br = null;
  30.         // If a null string was entered allow entity selection
  31.         if (pr.StringResult == "")
  32.         {
  33.           // Select a block reference
  34.           PromptEntityOptions peo =
  35.             new PromptEntityOptions(
  36.               "\nSelect dynamic block reference: "
  37.             );
  38.           peo.SetRejectMessage("\nEntity is not a block.");
  39.           peo.AddAllowedClass(typeof(BlockReference), false);
  40.           PromptEntityResult per =
  41.             ed.GetEntity(peo);
  42.           if (per.Status != PromptStatus.OK)
  43.             return;
  44.           // Access the selected block reference
  45.           br =
  46.             tr.GetObject(
  47.               per.ObjectId,
  48.               OpenMode.ForRead
  49.             ) as BlockReference;
  50.         }
  51.         else
  52.         {
  53.           // Otherwise we look up the block by name
  54.           BlockTable bt =
  55.             tr.GetObject(
  56.               db.BlockTableId,
  57.               OpenMode.ForRead) as BlockTable;
  58.           if (!bt.Has(pr.StringResult))
  59.           {
  60.             ed.WriteMessage(
  61.               "\nBlock "" + pr.StringResult + "" does not exist."
  62.             );
  63.             return;
  64.           }
  65.           // Create a new block reference referring to the block
  66.           br =
  67.             new BlockReference(
  68.               new Point3d(),
  69.               bt[pr.StringResult]
  70.             );
  71.         }
  72.         BlockTableRecord btr =
  73.           (BlockTableRecord)tr.GetObject(
  74.             br.DynamicBlockTableRecord,
  75.             OpenMode.ForRead
  76.           );
  77.         // Call our function to display the block properties
  78.         DisplayDynBlockProperties(ed, br, btr.Name);
  79.         // Committing is cheaper than aborting
  80.         tr.Commit();
  81.       }
  82.     }
  83.     private static void DisplayDynBlockProperties(
  84.       Editor ed, BlockReference br, string name
  85.     )
  86.     {
  87.       // Only continue is we have a valid dynamic block
  88.       if (br != null && br.IsDynamicBlock)
  89.       {
  90.         ed.WriteMessage(
  91.           "\nDynamic properties for "{0}"\n",
  92.           name
  93.         );
  94.         // Get the dynamic block's property collection
  95.         DynamicBlockReferencePropertyCollection pc =
  96.           br.DynamicBlockReferencePropertyCollection;
  97.         // Loop through, getting the info for each property
  98.         foreach (DynamicBlockReferenceProperty prop in pc)
  99.         {
  100.           // Start with the property name, type and description
  101.           ed.WriteMessage(
  102.             "\nProperty: "{0}" : {1}",
  103.             prop.PropertyName,
  104.             prop.UnitsType
  105.           );
  106.           if (prop.Description != "")
  107.             ed.WriteMessage(
  108.               "\n  Description: {0}",
  109.               prop.Description
  110.             );
  111.           // Is it read-only?
  112.           if (prop.ReadOnly)
  113.             ed.WriteMessage(" (Read Only)");
  114.           // Get the allowed values, if it's constrained
  115.           bool first = true;
  116.           foreach (object value in prop.GetAllowedValues())
  117.           {
  118.             ed.WriteMessage(
  119.               (first ? "\n  Allowed values: [" : ", ")
  120.             );
  121.             ed.WriteMessage(""{0}"", value);
  122.             first = false;
  123.           }
  124.           if (!first)
  125.             ed.WriteMessage("]");
  126.           // And finally the current value
  127.           ed.WriteMessage(
  128.             "\n  Current value: "{0}"\n",
  129.             prop.Value
  130.           );
  131.         }
  132.       }
  133.     }
  134.   }
  135. }
Here's what happens when we run the DBP command, selecting the "Hex Socket Bolt (Side) - Metric" block from the "Mechanical - Metric.dwg" file in the Samples\Dynamic Blocks folder of your AutoCAD installation.
  1. Command: DBP
  2. Enter dynamic block name or enter to select:
  3. Select dynamic block reference:
  4. Dynamic properties for "Hex Socket Bolt (Side) - Metric"
  5. Property: "d1" : Distance
  6.   Allowed values: ["3", "4", "5", "6", "8", "10", "12", "14", "16", "20", "24",
  7. "27", "30", "36"]
  8.   Current value: "14"
  9. Property: "Origin" : NoUnits (Read Only)
  10.   Current value: "(97.714,-5,0)"
  11. Property: "b" : Distance
  12.   Allowed values: ["18", "20", "22", "24", "28", "32", "36", "40", "44", "52",
  13. "57", "60", "65", "66", "72", "73", "84", "85"]
  14.   Current value: "40"
  15. Property: "Origin" : NoUnits (Read Only)
  16.   Current value: "(100,-3.5,0)"
  17. Property: "k" : Distance
  18.   Allowed values: ["3", "4", "5", "6", "8", "10", "12", "14", "16", "20", "24",
  19. "27", "30", "36"]
  20.   Current value: "14"
  21. Property: "Origin" : NoUnits (Read Only)
  22.   Current value: "(5.64204767561892E-15,-7,0)"
  23. Property: "d2" : Distance
  24.   Allowed values: ["5.5", "7", "8.5", "10", "13", "16", "18", "21", "24", "30",
  25. "36", "40", "45", "54"]
  26.   Current value: "21"
  27. Property: "Origin" : NoUnits (Read Only)
  28.   Current value: "(3.5527136788005E-15,-8,0)"
  29. Property: "Size" : NoUnits
  30.   Allowed values: ["M3", "M4", "M5", "M6", "M8", "M10", "M12", "M14"]
  31.   Current value: "M14"
  32. Property: "Visibility" : NoUnits
  33.   Allowed values: ["M3", "M4", "M5", "M6", "M8", "M10", "M12", "M14"]
  34.   Current value: "M14"
  35. Property: "Length (M3)" : Distance
  36.   Description: Set the bolt length
  37.   Allowed values: ["25", "30", "35", "40", "45", "50", "60"]
  38.   Current value: "100"
  39. Property: "Origin" : NoUnits (Read Only)
  40.   Current value: "(0,0,0)"
  41. Property: "Length (M4)" : Distance
  42.   Description: Set the bolt length
  43.   Allowed values: ["30", "35", "40", "45", "50", "60", "70", "80"]
  44.   Current value: "100"
  45. Property: "Origin" : NoUnits (Read Only)
  46.   Current value: "(0,0,0)"
  47. Property: "Length (M5)" : Distance
  48.   Description: Set the bolt length
  49.   Allowed values: ["30", "35", "40", "45", "50", "55", "60", "70", "80", "90",
  50. "100"]
  51.   Current value: "100"
  52. Property: "Origin" : NoUnits (Read Only)
  53.   Current value: "(0,0,0)"
  54. Property: "Length (M6)" : Distance
  55.   Description: Set the bolt length
  56.   Allowed values: ["35", "40", "45", "50", "55", "60", "65", "70", "75", "80",
  57. "90", "100", "110", "120"]
  58.   Current value: "100"
  59. Property: "Origin" : NoUnits (Read Only)
  60.   Current value: "(2.25597318603832E-14,0,0)"
  61. Property: "Length (M8)" : Distance
  62.   Description: Set the bolt length
  63.   Allowed values: ["40", "45", "50", "55", "60", "65", "70", "75", "80", "85",
  64. "90", "100", "110", "120", "130", "140", "150", "160", "180", "200"]
  65.   Current value: "100"
  66. Property: "Origin" : NoUnits (Read Only)
  67.   Current value: "(2.25597318603832E-14,0,0)"
  68. Property: "Length (M10)" : Distance
  69.   Description: Set the bolt length
  70.   Allowed values: ["45", "50", "55", "60", "65", "70", "75", "80", "85", "90",
  71. "100", "110", "120", "130", "140", "150", "160", "180", "200"]
  72.   Current value: "100"
  73. Property: "Origin" : NoUnits (Read Only)
  74.   Current value: "(2.25597318603832E-14,0,0)"
  75. Property: "Length (M12)" : Distance
  76.   Description: Set the bolt length
  77.   Allowed values: ["55", "60", "65", "70", "75", "80", "85", "90", "100",
  78. "110", "120", "130", "140", "150", "160", "180", "200"]
  79.   Current value: "100"
  80. Property: "Origin" : NoUnits (Read Only)
  81.   Current value: "(2.25597318603832E-14,0,0)"
  82. Property: "Length (M14)" : Distance
  83.   Description: Set the bolt length
  84.   Allowed values: ["60", "65", "70", "75", "80", "90", "100", "110", "120",
  85. "130", "140", "150", "160"]
  86.   Current value: "100"
  87. Property: "Origin" : NoUnits (Read Only)
  88.   Current value: "(2.25597318603832E-14,0,0)"
复制代码
Many of the properties are actually 0, but have ended up being printed as a very small number... it would be simple enough to check these against a tolerance rather than trusting them to be understood as being zero.
OK, that's a good enough start for today. In the next post we'll address the need to capture dynamic properties from an inserted dynamic block and copy them across to another, already-inserted dynamic block. A bit like a "property painter" for dynamic blocks. (If you're thinking that this doesn't sound quite like what Fernando originally asked for, then you'd be quite right. We exchanged a few emails, and I then opted for a "property painter" approach to address the problem.)
Thanks for the inspiration, Fernando and Philippe! :-)

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-4-20 11:10 , Processed in 4.620935 second(s), 17 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表