Author Topic: Can't use DrawableOverrule on AcDbText  (Read 1266 times)

0 Members and 1 Guest are viewing this topic.

TT

  • Swamp Rat
  • Posts: 826
Can't use DrawableOverrule on AcDbText
« on: August 17, 2012, 07:41:29 pm »
It looks like there's no way to do custom drawing for AcDbText because of this bug.

And, professional responsibility requires those that have published tutorials
and/or example code that touches this defect, to issue a warning to their
audience.

If you'd like to confirm it, here's the source code and repro steps:

Code - C#: [Select]
  1.  
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using Autodesk.AutoCAD.DatabaseServices;
  7. using Autodesk.AutoCAD.Runtime;
  8. using Autodesk.AutoCAD.GraphicsInterface;
  9. using Autodesk.AutoCAD.ApplicationServices;
  10.  
  11. namespace DrawableOverruleBug
  12. {
  13.   /// A completely do-nothing DrawableOverrule on DBText that demonstrates
  14.   /// an API bug in the Overrule managed wrapper API.
  15.   ///
  16.   /// The bug manifests when there is a block containing TEXT that's
  17.   /// inserted into a table cell, and the drawing is saved.
  18.   ///
  19.   /// To reproduce the bug:
  20.   ///  
  21.   /// - In a new drawing, define a block that contains one TEXT object
  22.   ///   (that's TEXT, not MTEXT).
  23.   ///
  24.   /// - Insert an empty table into the drawing.
  25.   ///
  26.   /// - Click on a table cell, then right click and choose Insert -> Block,
  27.   ///   and select the block containing the text, and then click OK.
  28.   ///
  29.   /// - NETLOAD the assembly containing this code.
  30.   ///
  31.   /// - Issue the WORLDDRAWTEST command to enable the overrule.
  32.   ///
  33.   /// - Issue the QSAVE command.
  34.   ///
  35.   /// This bug effectively makes it impossible to overrule AcDbText,
  36.  
  37.   class WorldDrawTest : DrawableOverrule
  38.   {
  39.      public WorldDrawTest()
  40.      {
  41.         Overrule.AddOverrule( RXClass.GetClass( typeof( DBText ) ), this, true );
  42.         Overrule.Overruling = true;
  43.      }
  44.  
  45.      public override bool WorldDraw( Drawable drawable, WorldDraw wd )
  46.      {
  47.         try
  48.         {
  49.            return base.WorldDraw( drawable, wd );
  50.         }
  51.         catch( System.Exception ex )
  52.         {
  53.            Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage( ex.ToString() );
  54.            return true;
  55.         }
  56.      }
  57.   }
  58.  
  59.  
  60.   public static class Commands
  61.   {
  62.      static WorldDrawTest worldDrawTest = null;
  63.  
  64.      [CommandMethod("WORLDDRAWTEST")]
  65.      public static void WorldDrawTest()
  66.      {
  67.         if( worldDrawTest == null )
  68.            worldDrawTest = new WorldDrawTest();
  69.  
  70.         Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(
  71.            "\nDrawableOverrule enabled, issue the QSAVE command." );
  72.      }
  73.   }
  74. }
  75.  
  76.  

nullptr

  • Bricscad
  • Needs a day job
  • Posts: 6584
  • AKA Daniel
Re: Can't use DrawableOverrule on AcDbText
« Reply #1 on: August 17, 2012, 08:37:21 pm »
Can you test the owner?

TT

  • Swamp Rat
  • Posts: 826
Re: Can't use DrawableOverrule on AcDbText
« Reply #2 on: August 17, 2012, 10:58:49 pm »
Can you test the owner?

I don't know if testing the owner would help, since avoiding the call to
the base class's WorldDraw() would result in nothing being drawn, and
it would prevent ViewportDraw() from being called if the return value of
the former were false.

I'm still looking for a solution.
« Last Edit: August 17, 2012, 11:13:48 pm by TT »

TT

  • Swamp Rat
  • Posts: 826
Re: Can't use DrawableOverrule on AcDbText
« Reply #3 on: August 17, 2012, 11:30:19 pm »
This also confirms that it's a bug in the API:

Code: [Select]
System.InvalidCastException: Unable to cast object of type
'Autodesk.AutoCAD.GraphicsInterface.ImpSubEntityTraits' to type
'Autodesk.AutoCAD.GraphicsInterface.DrawableTraits'.
   at AcMgDrawableOverrule.setAttributes(AcMgDrawableOverrule* , AcGiDrawable*
pThis, AcGiDrawableTraits* traits)
   at AcGiDrawableOverrule.worldDraw(AcGiDrawableOverrule* , AcGiDrawable* ,
AcGiWorldDraw* )
   at Autodesk.AutoCAD.GraphicsInterface.DrawableOverrule.WorldDraw(Drawable
drawable, WorldDraw wd)
   at DrawableOverruleBug.WorldDrawTest.WorldDraw(Drawable drawable, WorldDraw wd)

TT

  • Swamp Rat
  • Posts: 826
Re: DrawableOverrule is completely unusable
« Reply #4 on: August 17, 2012, 11:53:52 pm »
Well it looks like I was wrong, and so I've changed the subject of this thread to reflect what it appears to be, that you cannot use DrawableOverrule at all, because the aforementioned bug doesn't apply to only TEXT, it applies to everything.

Here's the output of a tool I was writing to trace WorldDraw calls to blocks and text:

Code: [Select]

// Expected output:
BeginContainer:  AcDbTable (0x1F7) Name: *T4 Level: 0
  BeginContainer:  AcDbBlockReference (0x226) Name: A Level: 1
    Drawing: AcDbText (0x1BE) Level = 2
  EndContainer:    AcDbBlockReference (0x226) Name: A Level: 1
EndContainer:    AcDbTable (0x1F7) Name: *T4 Level: 0
Command: QSAVE

// Actual result during QSAVE:

BeginContainer:  AcDbTable (0x1F7) Name: *T4 Level: 0
  BeginContainer:  AcDbBlockReference (0x226) Name: A Level: 1
  EndContainer:    AcDbBlockReference (0x226) Name: A Level: 1

AcDbBlockReference (0x226) Name: A -> : System.InvalidCastException:
Unable to cast object of type 'Autodesk.AutoCAD.GraphicsInterface.ImpSubEntityTraits' to type
'Autodesk.AutoCAD.GraphicsInterface.DrawableTraits'.
   at AcMgDrawableOverrule.setAttributes(AcMgDrawableOverrule* , AcGiDrawable*
pThis, AcGiDrawableTraits* traits)
   at AcGiDrawableOverrule.worldDraw(AcGiDrawableOverrule* , AcGiDrawable* ,
AcGiWorldDraw* )
   at Autodesk.AutoCAD.GraphicsInterface.DrawableOverrule.WorldDraw(Drawable
drawable, WorldDraw wd)
   at CompoundDrawableOverrules.DrawableOverrule`1.BaseWorldDraw(Drawable
drawable, WorldDraw wd) in CompoundDrawable.cs:line 87


In this case, it blew up in the call to base.WorldDraw() in an overrule targeting BlockReference, rather than text.

If you can't use DrawableOverrule on drawings that contain tables with blocks inserted in them, then you can't use it PERIOD - which is like it never existed in the first place.
« Last Edit: August 18, 2012, 12:01:59 am by TT »

Jeff H

  • Needs a day job
  • Posts: 4606
Re: Can't use DrawableOverrule on AcDbText
« Reply #5 on: August 18, 2012, 12:40:19 am »
I was going to reply just joking around that Autodesk's solution was just not to override any methods in a class that derives from an overrule, but you can't even do that!!
 
Adding the overrule was all it took to go KABOOM!
Code - C#: [Select]
  1.  
  2.          Overrule.AddOverrule(RXClass.GetClass(typeof(DBText)), worldDrawTest, false);
  3.         Overrule.Overruling = true;
  4.  

TT

  • Swamp Rat
  • Posts: 826
Re: Can't use DrawableOverrule on AcDbText
« Reply #6 on: August 18, 2012, 12:51:50 am »
I was going to reply just joking around that Autodesk's solution was just not to override any methods in a class that derives from an overrule, but you can't even do that!!
 
Adding the overrule was all it took to go KABOOM!
Code - C#: [Select]
  1.  
  2.          Overrule.AddOverrule(RXClass.GetClass(typeof(DBText)), worldDrawTest, false);
  3.         Overrule.Overruling = true;
  4.  

Adding the overrule shouldn't have caused a crash.

Did you try the exact same code I posted?

Jeff H

  • Needs a day job
  • Posts: 4606
Re: Can't use DrawableOverrule on AcDbText
« Reply #7 on: August 18, 2012, 01:11:55 am »
Yes sir,
 
and did the following steps and it crashed each time.
 
-I tried the exact code you posted.
-I commented out WorldDraw Method
-Changed bAtLast to false
-Removed constructor and called AddOverrule in command.

This is how code ended up
Code - C#: [Select]
  1.   class WorldDrawTest : DrawableOverrule
  2.   {
  3.      //public WorldDrawTest()
  4.      //{
  5.      //   Overrule.AddOverrule( RXClass.GetClass( typeof( DBText ) ), this, true );
  6.      //   Overrule.Overruling = true;
  7.      //}
  8.      //public override bool WorldDraw( Drawable drawable, WorldDraw wd )
  9.      //{
  10.      //   try
  11.      //   {
  12.      //      return base.WorldDraw( drawable, wd );
  13.      //   }
  14.      //   catch( System.Exception ex )
  15.      //   {
  16.      //      Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage( ex.ToString() );
  17.      //      return true;
  18.      //   }
  19.      //}
  20.   }
  21.  
  22.   public static class Commands
  23.   {
  24.      static WorldDrawTest worldDrawTest = null;
  25.  
  26.        [CommandMethod("WORLDDRAWTEST")]
  27.        public static void WorldDrawTest()
  28.        {
  29.            if (worldDrawTest == null)
  30.                worldDrawTest = new WorldDrawTest();
  31.            Overrule.AddOverrule(RXClass.GetClass(typeof(DBText)), worldDrawTest, false);
  32.            Overrule.Overruling = true;
  33.            Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(
  34.               "\nDrawableOverrule enabled, issue the QSAVE command.");
  35.        }
  36.   }
  37.  

 *****Edit*************
The first test I followed your steps the others I saved a drawing with a table with block inserted and followed these steps for each drawing
-QSave
-NetLoad
-WorldDrawTest
-QSave
-KABOOM!
« Last Edit: August 18, 2012, 01:17:20 am by Jeff H »

owenwengerd

  • Bull Frog
  • Posts: 366
Re: Can't use DrawableOverrule on AcDbText
« Reply #8 on: August 18, 2012, 01:12:11 am »
In ObjectARX you would check AcGiCommonDraw::regenType() to handle the proxy graphics generation differently. Perhaps the same can be done here?

TT

  • Swamp Rat
  • Posts: 826
Re: Can't use DrawableOverrule on AcDbText
« Reply #9 on: August 18, 2012, 01:41:04 am »
In ObjectARX you would check AcGiCommonDraw::regenType() to handle the proxy graphics generation differently. Perhaps the same can be done here?

Yep - That's one of the workarounds I've tried, but I still can't tell what effect it's having. I think the calls to WorldDraw() are to generate a preview image but can't be sure.  I checked the preview image and it seems to show the overruled drawing even though I'm disabling overruling for all WorldDraw() calls nested within the WorldDraw() call on the insertion of the AcDbTable:

Code - C#: [Select]
  1. // Call this instead of base.WorldDraw()
  2.  
  3. protected virtual bool SafeBaseWorldDraw( Drawable drawable, WorldDraw wd )
  4. {
  5.   if( wd.RegenType == RegenType.SaveWorldDrawForProxy && drawable is Table )
  6.   {
  7.      Overruling = false;   // disable all overruling
  8.      try
  9.      {
  10.         return base.WorldDraw( drawable, wd );
  11.      }
  12.      finally
  13.      {
  14.         Overruling = true;
  15.      }
  16.   }
  17.   return base.WorldDraw( drawable, wd );
  18. }
  19.  

TT

  • Swamp Rat
  • Posts: 826
Re: Can't use DrawableOverrule on AcDbText
« Reply #10 on: August 18, 2012, 01:47:37 am »
Thanks for confirming. See my reply to Owen regarding a possible workaround.  It avoids the crash, but I still don't know what other side effects it may have.

Yes sir,
 
and did the following steps and it crashed each time.
 
-I tried the exact code you posted.
-I commented out WorldDraw Method
-Changed bAtLast to false
-Removed constructor and called AddOverrule in command.

This is how code ended up
Code - C#: [Select]
  1.   class WorldDrawTest : DrawableOverrule
  2.   {
  3.      //public WorldDrawTest()
  4.      //{
  5.      //   Overrule.AddOverrule( RXClass.GetClass( typeof( DBText ) ), this, true );
  6.      //   Overrule.Overruling = true;
  7.      //}
  8.      //public override bool WorldDraw( Drawable drawable, WorldDraw wd )
  9.      //{
  10.      //   try
  11.      //   {
  12.      //      return base.WorldDraw( drawable, wd );
  13.      //   }
  14.      //   catch( System.Exception ex )
  15.      //   {
  16.      //      Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage( ex.ToString() );
  17.      //      return true;
  18.      //   }
  19.      //}
  20.   }
  21.  
  22.   public static class Commands
  23.   {
  24.      static WorldDrawTest worldDrawTest = null;
  25.  
  26.        [CommandMethod("WORLDDRAWTEST")]
  27.        public static void WorldDrawTest()
  28.        {
  29.            if (worldDrawTest == null)
  30.                worldDrawTest = new WorldDrawTest();
  31.            Overrule.AddOverrule(RXClass.GetClass(typeof(DBText)), worldDrawTest, false);
  32.            Overrule.Overruling = true;
  33.            Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(
  34.               "\nDrawableOverrule enabled, issue the QSAVE command.");
  35.        }
  36.   }
  37.  

 *****Edit*************
The first test I followed your steps the others I saved a drawing with a table with block inserted and followed these steps for each drawing
-QSave
-NetLoad
-WorldDrawTest
-QSave
-KABOOM!

Jeff H

  • Needs a day job
  • Posts: 4606
Re: Can't use DrawableOverrule on AcDbText
« Reply #11 on: August 18, 2012, 02:04:20 am »
I think I tried for 2013 and it did not work but below snips from sending ADN a question how in 2011 setting Overruling to false worked but in 2012 it did not.
Now I think about it something might have gotten cached but I think in debugging it with 2012 and Overruling set to false it still called the overriden methods
Quote
Your modified code looks correct. You are basically removing the added overrule from for the line entity in your modified code.  I will be consulting my engineering team on your issue (Overrule.Overruling = Not Overrule.Overruling failing in 2012) and I will get back to you as soon as I get some relevant information.
 
Solution
Quote
 
 As mentioned in earlier mail, you need to remove overrule using API “RemoveOverrule” instead of toggling variable “Overruling”.

Jeff H

  • Needs a day job
  • Posts: 4606
Re: Can't use DrawableOverrule on AcDbText
« Reply #12 on: August 18, 2012, 11:38:22 am »
See my reply to Owen regarding a possible workaround. 
I guess in your example you were overruling Entity or Drawable?
 
I have not spent much time with it but any call to base.WorldDraw() threw an error. The Invalid cast ImpSubEntityTraits.
 
I thought maybe anything that derived from a BlockReference containing the block might also throw an error but creating a MInsertBlock with block did not.
 
This was the only thing I got working and is your test code with a added overrule for tables, that is added in BeginSave event and removed in SaveComplete Event.
 
Setting Overruling to false did not change a thing but it could be something I missing.
This was what I had left and NOT meant as a solution.
Code - C#: [Select]
  1.  
  2.  class WorldDrawTest : DrawableOverrule
  3.    {
  4.        public WorldDrawTest()
  5.        {
  6.            Overrule.AddOverrule(RXClass.GetClass(typeof(DBText)), this, true);
  7.            Overrule.Overruling = true;
  8.        }
  9.        public override bool WorldDraw(Drawable drawable, WorldDraw wd)
  10.        {
  11.            try
  12.            {
  13.                return base.WorldDraw(drawable, wd);
  14.            }
  15.            catch (System.Exception ex)
  16.            {
  17.                Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(ex.ToString());
  18.                return true;
  19.            }
  20.        }
  21.    }
  22.  
  23.    class TableBug : DrawableOverrule
  24.    {
  25.        public TableBug()
  26.        {
  27.            Overrule.AddOverrule(RXClass.GetClass(typeof(Table)), this, true);
  28.            Overrule.Overruling = true;
  29.        }
  30.        public override bool WorldDraw(Drawable drawable, WorldDraw wd)
  31.        {
  32.            return wd.RegenType == RegenType.SaveWorldDrawForProxy ? true : base.WorldDraw(drawable, wd);
  33.           //return base.WorldDraw(drawable, wd);/////Throws error everytime
  34.        }      
  35.    }
  36.  
  37.    public static class Commands
  38.    {
  39.        static WorldDrawTest worldDrawTest = null;
  40.        static TableBug tableBug = null;
  41.  
  42.        [CommandMethod("WORLDDRAWTEST")]
  43.        public static void WorldDrawTest()
  44.        {
  45.            if (worldDrawTest == null)
  46.                worldDrawTest = new WorldDrawTest();
  47.            Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(
  48.               "\nDrawableOverrule enabled, issue the QSAVE command.");
  49.            HostApplicationServices.WorkingDatabase.BeginSave += new DatabaseIOEventHandler(WorkingDatabase_BeginSave);
  50.            HostApplicationServices.WorkingDatabase.SaveComplete += new DatabaseIOEventHandler(WorkingDatabase_SaveComplete);
  51.        }
  52.        static void WorkingDatabase_SaveComplete(object sender, DatabaseIOEventArgs e)
  53.        {
  54.            Overrule.RemoveOverrule(RXClass.GetClass(typeof(Table)), tableBug);
  55.        }
  56.        static void WorkingDatabase_BeginSave(object sender, DatabaseIOEventArgs e)
  57.        {
  58.            if (tableBug == null)
  59.            {
  60.                tableBug = new TableBug();
  61.            }
  62.            else
  63.            {
  64.                Overrule.AddOverrule(RXClass.GetClass(typeof(Table)), tableBug, true);
  65.            }
  66.        }
  67.    }
  68.  

Jeff H

  • Needs a day job
  • Posts: 4606
Re: Can't use DrawableOverrule on AcDbText
« Reply #13 on: August 18, 2012, 11:49:54 am »
I linked this to another thread at Autodesk Forums http://forums.autodesk.com/t5/NET/Overrule-a-specific-subentity-in-block-and-not-showing-the-Base/m-p/3586752#M30649 and looks like you died.
 
Sorry to hear your dead Tony, hopefully you will get over it and start feeling better.
« Last Edit: August 18, 2012, 01:07:42 pm by Jeff H »

TT

  • Swamp Rat
  • Posts: 826
Re: Can't use DrawableOverrule on AcDbText
« Reply #14 on: August 19, 2012, 03:59:26 am »
See my reply to Owen regarding a possible workaround. 
I guess in your example you were overruling Entity or Drawable?
 
I have not spent much time with it but any call to base.WorldDraw() threw an error. The Invalid cast ImpSubEntityTraits.
 
I thought maybe anything that derived from a BlockReference containing the block might also throw an error but creating a MInsertBlock with block did not.
 
This was the only thing I got working and is your test code with a added overrule for tables, that is added in BeginSave event and removed in SaveComplete Event.
 
Setting Overruling to false did not change a thing but it could be something I missing.
This was what I had left and NOT meant as a solution.
Code - C#: [Select]
  1.  
  2.  class WorldDrawTest : DrawableOverrule
  3.    {
  4.        public WorldDrawTest()
  5.        {
  6.            Overrule.AddOverrule(RXClass.GetClass(typeof(DBText)), this, true);
  7.            Overrule.Overruling = true;
  8.        }
  9.        public override bool WorldDraw(Drawable drawable, WorldDraw wd)
  10.        {
  11.            try
  12.            {
  13.                return base.WorldDraw(drawable, wd);
  14.            }
  15.            catch (System.Exception ex)
  16.            {
  17.                Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(ex.ToString());
  18.                return true;
  19.            }
  20.        }
  21.    }
  22.  
  23.    class TableBug : DrawableOverrule
  24.    {
  25.        public TableBug()
  26.        {
  27.            Overrule.AddOverrule(RXClass.GetClass(typeof(Table)), this, true);
  28.            Overrule.Overruling = true;
  29.        }
  30.        public override bool WorldDraw(Drawable drawable, WorldDraw wd)
  31.        {
  32.            return wd.RegenType == RegenType.SaveWorldDrawForProxy ? true : base.WorldDraw(drawable, wd);
  33.           //return base.WorldDraw(drawable, wd);/////Throws error everytime
  34.        }      
  35.    }
  36.  
  37.    public static class Commands
  38.    {
  39.        static WorldDrawTest worldDrawTest = null;
  40.        static TableBug tableBug = null;
  41.  
  42.        [CommandMethod("WORLDDRAWTEST")]
  43.        public static void WorldDrawTest()
  44.        {
  45.            if (worldDrawTest == null)
  46.                worldDrawTest = new WorldDrawTest();
  47.            Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(
  48.               "\nDrawableOverrule enabled, issue the QSAVE command.");
  49.            HostApplicationServices.WorkingDatabase.BeginSave += new DatabaseIOEventHandler(WorkingDatabase_BeginSave);
  50.            HostApplicationServices.WorkingDatabase.SaveComplete += new DatabaseIOEventHandler(WorkingDatabase_SaveComplete);
  51.        }
  52.        static void WorkingDatabase_SaveComplete(object sender, DatabaseIOEventArgs e)
  53.        {
  54.            Overrule.RemoveOverrule(RXClass.GetClass(typeof(Table)), tableBug);
  55.        }
  56.        static void WorkingDatabase_BeginSave(object sender, DatabaseIOEventArgs e)
  57.        {
  58.            if (tableBug == null)
  59.            {
  60.                tableBug = new TableBug();
  61.            }
  62.            else
  63.            {
  64.                Overrule.AddOverrule(RXClass.GetClass(typeof(Table)), tableBug, true);
  65.            }
  66.        }
  67.    }
  68.  

Disabling the overrule while the drawing is being saved was one of the solutions I considered, but after giving it some thought, I didn't pursue it, because Overrules are applied to all objects regardless of what database they're in, and that can include objects in databases that aren't open in the editor, and so forth, so that solution would need to hook the Begin/End save events of every single database, which would be a real PITA.

After investigating further, it turns out that this may not be a bug at all, but rather just the nature of the beast, which to summarize, is that customizing the display of compound objects is much more complicated than it might appear at first, and the solution to the problem may lie in overriding SetAttributes() and changing the flags it returns, but I've not had time to play with that yet.