How to get Point3D with actual Z value for a Dynamic Block which has multiple point parameters.

How to get Point3D with actual Z value for a Dynamic Block which has multiple point parameters.

dCaldwell_dotNET
Explorer Explorer
893 Views
12 Replies
Message 1 of 13

How to get Point3D with actual Z value for a Dynamic Block which has multiple point parameters.

dCaldwell_dotNET
Explorer
Explorer

I have a simple dynamic block which I give multiple point parameters. These will be used for the block insertion. Some of the point parameters are in the +Z direction.

darrell_caldwell9WEJK_0-1759165398722.png

 

If I use AutoCAD command INSERT, I can cycle between these points for the insertion grip location and the +Z points cycle correctly. I want to insert using a EntityJig. I currently can insert a block and jig from the origin only. I understand to access Dynamic Block Properties i have to use 

 

DynamicBlockReferencePropertyCollection

 

 

But here is where I have problems. For a point parameter, this only returns Position 1 X, Position 1 Y when I iterate of the collection. There is no value for Z. I know it work for the INSERT command so its stored somewhere. I need help to identify where these values could be stored.

0 Likes
Accepted solutions (1)
894 Views
12 Replies
Replies (12)
Message 2 of 13

ActivistInvestor
Mentor
Mentor

Dynamic blocks were by-design 2D only (don't ask why, I can't answer that).

 

You can have a look at this thread, which might offer some ideas.

0 Likes
Message 3 of 13

dCaldwell_dotNET
Explorer
Explorer

Thanks for the response.

 

And yes I have ran this before and as you can see it uses "0" for Z position as I mentioned before, the DYNAMICBLOCKPROPERTIES only returns X & Y values. I understand that dynamic blocks where designed to be 2d specific. But at the same time if AutoCAD can access the 3D Z position using the INSERT command, there should be some way to find that Z value in the block reference or even the block definition.

0 Likes
Message 4 of 13

khodulieucuong
Contributor
Contributor

var z = br.Position.Z; do you want this?

0 Likes
Message 5 of 13

dCaldwell_dotNET
Explorer
Explorer

khodulieucuong,

 

No, that will only return the basepoint of the block. You can only have one basepoint per block. My block has multiple points that can be cycled using the CTRL keypress for insertion points. These are the points I want to get the Z value for.

0 Likes
Message 6 of 13

ActivistInvestor
Mentor
Mentor

How are you inserting the point parameters? The block editor doesn't allow you to insert point parameters with a non-zero Z ordinate.

0 Likes
Message 7 of 13

dCaldwell_dotNET
Explorer
Explorer

I am inserting the point parameters thru the Block Editor. Nothing special. I do make a change after I insert the point parameter. I select the points Standard Grip, the blue square, and move it in the Z direction to place it where I need for insertion. So yes you can insert point parameters with a non-zero Z ordinate. and it works perfectly using the INSERT command and pressing the CTRL key to cycle these grip points.

 

See attached block

0 Likes
Message 8 of 13

dCaldwell_dotNET
Explorer
Explorer

Just a note, you can also move the Grip Points for most everything in the Z-Direction. I also have blocks where my stretch grip are elevated in the Z, so the user will know which part he will be stretching. See below. the bottom stretch grips are for all three struts and the top 2 grips are for the top strut only. And again, this works as expected.

 

darrell_caldwell9WEJK_0-1759256220504.png

 

So based on these examples, I believe the DYNAMIC BLOCK PROPERTIES returns the parameters graphic location only and not that of the GRIPS. Which is why I cant access the Z values for these GRIPS. These parameter graphics can not be placed on non-Zero Z ordinates. So AutoCAD stores these GRIPS somewhere else.

 

0 Likes
Message 9 of 13

ActivistInvestor
Mentor
Mentor

I looked through the managed API and couldn't find anything that returns the insertion points for a dynamic block. I also didn't find any native APIs that did that. That doesn't mean one doesn't exist, but I don't have time to do an exhaustive search through dozens of libraries.

 

So it looks like the only solution is the brute-force one that involves the use of the EvalGraph and P/Invoking acdbEntGet(), which I've had to resort to before for similar purposes. The code below was very quickly adapted from code I wrote long ago for accessing visibility parameter info, and should give you what you're after.

 

public static partial class BlockReferenceExtensions
{
   /// <summary>
   /// Returns the alternate block insertion points for
   /// the given BlockReference, transformed to its ECS.
   /// </summary>
   /// <param name="blkref"></param>
   /// <param name="trans"></param>
   /// <returns></returns>
   /// <exception cref="ArgumentNullException"></exception>

   public static IEnumerable<Point3d> GetInsertionPoints(this BlockReference blkref, Transaction trans)
   {
      if(blkref is null)
         throw new ArgumentNullException(nameof(blkref));
      if(trans is null)
         throw new ArgumentNullException(nameof(trans));
      EvalGraph evalGraph = GetEvalGraph(blkref, trans);
      if(evalGraph != null)
      {
         var nodes = evalGraph.GetAllNodes();
         if(nodes != null & nodes.Length > 0)
         {
            var matrix = blkref.BlockTransform;
            foreach(uint id in nodes)
            {
               var node = evalGraph.GetNode(id, OpenMode.ForRead, trans);
               if(node.ObjectId.ObjectClass == BlockXYGripClass)
               {
                  foreach(TypedValue tv in node.EntGet())
                  {
                     if(tv.TypeCode == 1010)
                     {
                        yield return ((Point3d)tv.Value).TransformBy(matrix);
                        break;
                     }
                  }
               }
            }
         }
      }
   }

   public static EvalGraph GetEvalGraph(this BlockReference blkref, Transaction trans)
   {
      if(blkref is null)
         throw new ArgumentNullException(nameof(blkref));
      if(trans is null)
         throw new ArgumentNullException(nameof(trans));
      if(!blkref.IsDynamicBlock)
         return null;
      ObjectId btrId = blkref.DynamicBlockTableRecord;
      var btr = (BlockTableRecord)trans.GetObject(btrId, OpenMode.ForRead);
      var xdictId = btr.ExtensionDictionary;
      if(xdictId.IsNull)
         return null;
      var xdict = (DBDictionary)trans.GetObject(xdictId, OpenMode.ForRead);
      var result = xdict.GetAt("ACAD_ENHANCEDBLOCK");
      if(result.IsNull)
         return null;
      return (EvalGraph)trans.GetObject(result, OpenMode.ForRead);
   }

   /// <summary>
   /// TODO: You must change the filename "acdb26.dll" to 
   /// the name of the file for the targeted AutoCAD release
   /// (e.g., "acdb24.dll", "acdb25.dll", etc.)
   /// </summary>
   const string ACDB_DLL = "acdb26.dll";

   const string ACCORE_DLL = "accore.dll";

   [DllImport(ACCORE_DLL, CallingConvention = CallingConvention.Cdecl,
      EntryPoint = "acdbEntGet")]
   static extern IntPtr acdbEntGet(AdsName ename);

   [DllImport(ACDB_DLL, CallingConvention = CallingConvention.Cdecl,
      EntryPoint = "?acdbGetAdsName@@YA?AW4ErrorStatus@Acad@@AEAY01_JVAcDbObjectId@@@Z")]
   static extern ErrorStatus acdbGetAdsName(out AdsName ename, ObjectId id);

   public static AdsName GetAdsName(this ObjectId id)
   {
      if(id.IsNull)
         throw new ArgumentException(nameof(id));
      AdsName result;
      var es = acdbGetAdsName(out result, id);
      if(es != ErrorStatus.OK)
         throw new Autodesk.AutoCAD.Runtime.Exception(es);
      return result;
   }

   static ResultBuffer EntGet(this ObjectId id)
   {
      if(id.IsNull)
         throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NullObjectId);
      var result = acdbEntGet(GetAdsName(id));
      if(result != IntPtr.Zero)
         return ResultBuffer.Create(result, true);
      return null;
   }

   static RXClass BlockXYGripClass = 
      (RXClass)SystemObjects.ClassDictionary["AcDbBlockXYGrip"];
}

 

Message 10 of 13

dCaldwell_dotNET
Explorer
Explorer

Thanks for the code  ActivistInvestor, I will try it out this weekend. I did see your visibility code which is how I started looking at the EvalGraph and nodes route.

I have been poking around the EvalGraph and nodes a little and have not been able to locate these points. But it I did find the point parameters by name. Just haven't located the 3d point. This P/Invoking route is new to me in my coding experience.

 

Also, I did find this in the "dpappgrip.h" header file.

class AcDbBlockInsertionPoints :  public AcRxProtocolReactor
{
    public:

        ACRX_DECLARE_MEMBERS(AcDbBlockInsertionPoints);

        /// <summary>
        /// Returns the application-defined insertion points for a block.
        /// </summary>
        ///
        /// <param name="pBlock">
        /// A pointer to the block whose insertion points should be elaborated. 
        /// </param>
        /// <param name="pBlkRef">
        /// A pointer to the block reference whose insertion points should be elaborated.
        /// The application can choose to use pBlkRef instead of pBlock, if the block is
        /// being dragged and insertion points or alignment angles change dynamically.
        /// </param>
        /// <param name="insPts">
        /// An array of AcGePoint3d objects, one for each insertion point. The
        /// returned points should be in the coordinate system of the block. 
        /// </param>
        /// <param name="alignmentDirections">
        /// An array of AcGeVector3d objects, one for each insertion point. The
        /// returned vectors describe the alignment direction for the block
        /// when the insertion point passes over other geometry in the drawing. 
        /// </param>
        /// 
        /// <returns>
        /// Acad::eOk if successful.
        /// </returns>
        ///
        /// <remarks>
        /// Any application-defined insertion points supplement the default
        /// insertion point for a block. Some commands (e.g. INSERT) allow
        /// users to cycle through the insertion points of a block when placing
        /// it in a drawing.
        /// Some commands (e.g. INSERT) use the alignment vector for a grip to
        /// automatically rotate the block as it is being dragged for insertion
        /// when the cursor passes over other entities in the drawing. If the
        /// alignment vector for a block is non-zero, when the block passes
        /// over other geometry in the drawing the block is rotated about the
        /// insertion point to make the alignment direction parallel to the
        /// normal of the entity under the cursor at the point closest to the
        /// cursor on the entity. 
        /// The size of the returned arrays must be the same.
        /// </remarks>
        virtual Acad::ErrorStatus getInsertionPoints (
                        const AcDbBlockTableRecord* pBlock,
                        const AcDbBlockReference*   pBlkRef,
                        AcGePoint3dArray&           insPts,
                        AcGeVector3dArray&          alignmentDirections) = 0;
};

 Not sure if this is usable or not but its seems to do what I am looking for. I don't need to know the actual parameters, just need the insertion points to implement a Jig and allow user to cycle thru with the CTRL key.

0 Likes
Message 11 of 13

ActivistInvestor
Mentor
Mentor

@dCaldwell_dotNET wrote:

Thanks for the code  ActivistInvestor, I will try it out this weekend. I did see your visibility code which is how I started looking at the EvalGraph and nodes route.

I have been poking around the EvalGraph and nodes a little and have not been able to locate these points. But it I did find the point parameters by name. Just haven't located the 3d point. This P/Invoking route is new to me in my coding experience.

 

Also, I did find this in the "dpappgrip.h" header file. what I am looking for. I don't need to know the actual parameters, just need the insertion points to implement a Jig and allow user to cycle thru with the CTRL key.


AcDbBlockInsertionPoints provides a way for applications to supply custom insertion points to AutoCAD's INSERT command dynamically, where they can be computed when requested. AutoCAD uses it internally to supply the INSERT command with alternate insertion points defined in dynamic blocks, but it can be also used to provide alternate insertion points for any block. It is exposed to managed code in the form of an event (The BlockTableRecord's static BlockInsertionPoints event).

 

However, it is not useful for your purposes, because AutoCAD polls the event when it needs alternate insertion points (usually during the INSERT command). There may be a way to get the protocol reactor instance and P/Invoke getInsertionPoints(), but getting a pointer to the instance of the protocol reactor from managed code is fairly complicated, requiring the use of a 3 or 4 native ObjectARX classes.

 

In any case, the code I posted will give you the insertion points.

 

0 Likes
Message 12 of 13

dCaldwell_dotNET
Explorer
Explorer

So I ran the code @ActivistInvestor, and I had a runtime error and had to change the following line of code.

 

From: 

foreach(TypedValue tv in node.EntGet())

To:

foreach(TypedValue tv in EntGet(node.ObjectId))

 

And it worked perfectly on the block i was testing. I hade 4 grip points I was after and it returned 4 points with the correct values of the block reference.

 

I have a couple of questions on how @ActivistInvestor was able to determine the AcDbBockXYGrip object and the implementing the TypedValue. Is there any documentation so I can learn how to implement something like this in the future? I am self taught when it comes to programming and always want to improve my knowledge, of course AutoCAD focused.

 

Again,

Thank you to @ActivistInvestor for the quick response and code.

 

0 Likes
Message 13 of 13

ActivistInvestor
Mentor
Mentor
Accepted solution

You got the error because I neglected to include the overload of EntGet() that takes a DBObject (and just calls the other overload with its ObjectId).

 

Nothing contained in an EvalGraph is documented, so it basically amounts to hacking. You just iterate over all nodes and dump the contents of the ResultBuffer returned by calling EntGet() on each node to the console, and look for Point3d objects. That tells you what underlying type(s) expose them, and then you can modify the code to look at only those types, and get the Points from them.

 

The tricky part is that the nodes in an EvalGraph are all exposed as DBObject because there are no managed wrappers for the actual underlying types. So, to find out what the underlying type is, you have to look at the ObjectClass property of the node's ObjectId. IOW, you can't simply do 'if(node is AcDbBockXYGrip)' because there is no managed wrapper for an AcDbBockXYGrip. So you have to get the AcDbBlockXYGrip runtime class from the class dictionary, and then use if(node.ObjectId.ObjectClass.IsDerivedFrom(<runtimeclass>)) to find the AcDbBlockXYGrip nodes.

 

The annoying part about having to P/Invoke acdbEntGet() is that it also requires P/Invoking acdbGetAdsName() which is in a DLL that has a version-dependent filename ('acdb23.dll', 'acdb24.dll', etc.) as noted in the comments in the code above. That in-turn makes any code that uses acdbEntGet() version-dependent. I came up with a workaround for that issue that allows you to avoid the version-dependent filename nonsense by using the WinAPI GetProcAddress() function to dynamically import acdbGetAdsName(), allowing the code that uses it to target any AutoCAD release. You can find that solution here.