Kean Walmsley

July 2009

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Twitter Updates

    follow me on Twitter



    « F# takes an important step closer to official release | Main | Preventing a .NET module from being loaded by AutoCAD »

    September 05, 2008

    No muttering at the back! Reducing the background noise when sending commands to AutoCAD

    Since posting three different options for Zooming to a Window or Entity inside AutoCAD, I've had a few discussions with a developer on how best to implement this cleanly. The requirement is to change the AutoCAD view via a smooth view transition (currently not exposed via any kind of view-modification API, only via the ZOOM command), but also to hide the fact we're sending commands to the command-line to do so.

    While we were discussing, I remembered an old friend, the NOMUTT system variable, which allows almost all command-line noise to be filtered out - even the "Command:" prompt disappears. While this technique is useful for this specific situation, it's also potentially very useful for many other instances where sending commands are currently the best available method of accessing particular functionality inside AutoCAD.

    A word of caution: setting NOMUTT to 1 can lead to severe user-disorientation. Please remember to set the variable back to 0, afterwards!

    Here's the modified C# code showing this technique - this time using SendStringToExecute() from the .NET API, rather than the COM API's synchronous SendCommand():

    using Autodesk.AutoCAD.ApplicationServices;

    using Autodesk.AutoCAD.DatabaseServices;

    using Autodesk.AutoCAD.EditorInput;

    using Autodesk.AutoCAD.Runtime;

    using Autodesk.AutoCAD.Geometry;


    namespace ZoomSmoothlyAndQuietly

    {

      public class Commands

      {

        // Zoom to a window specified by the user


        [CommandMethod("LZW")]

        static public void LoudZoomWindow()

        {

          Document doc =

            Application.DocumentManager.MdiActiveDocument;

          Editor ed = doc.Editor;


          Point2d min, max;

          if (GetWindowForZoom(ed, out min, out max))

            ZoomWin(ed, min, max, false);

        }


        [CommandMethod("QZW")]

        static public void QuietZoomWindow()

        {

          Document doc =

            Application.DocumentManager.MdiActiveDocument;

          Editor ed = doc.Editor;


          Point2d min, max;

          if (GetWindowForZoom(ed, out min, out max))

            ZoomWin(ed, min, max, true);

        }


        // Get the coordinates for a zoom


        static private bool GetWindowForZoom(

          Editor ed, out Point2d min, out Point2d max

        )

        {

          min = new Point2d();

          max = new Point2d();


          PromptPointOptions ppo =

            new PromptPointOptions(

              "\nSpecify first corner:"

            );


          PromptPointResult ppr =

            ed.GetPoint(ppo);


          if (ppr.Status != PromptStatus.OK)

            return false;


          min =

            new Point2d(ppr.Value.X, ppr.Value.Y);


          PromptCornerOptions pco =

            new PromptCornerOptions(

              "\nSpecify opposite corner: ",

              ppr.Value

            );


          ppr = ed.GetCorner(pco);


          if (ppr.Status != PromptStatus.OK)

            return false;


          max =

            new Point2d(ppr.Value.X, ppr.Value.Y) ;


          return true;

        }


        // Zoom by sending a command


        private static void ZoomWin(

          Editor ed, Point2d min, Point2d max, bool quietly

        )

        {

          string lower =

            min.ToString().Substring(

              1,

              min.ToString().Length - 2

            );


          string upper =

            max.ToString().Substring(

              1,

              max.ToString().Length - 2

            );


          string cmd =

            "_.ZOOM _W " + lower + " " + upper + " ";


          if (quietly)

          {

            // Get the old value of NOMUTT


            object nomutt =

              Application.GetSystemVariable("NOMUTT");


            // Add the string to reset NOMUTT afterwards


            cmd += "_NOMUTT " + nomutt.ToString() + " ";


            // Set NOMUTT to 1, reducing cmd-line noise


            Application.SetSystemVariable("NOMUTT", 1);

          }


          // Send the command(s)


          ed.Document.SendStringToExecute(

            cmd, true, false, !quietly

          );

        }

      }

    }

    Here's what happens when we run the "loud" command (LZW), and then the "quiet" one (QZW):

    Command: LZW

    Specify first corner:

    Specify opposite corner:

    Command: _.ZOOM

    Specify corner of window, enter a scale factor (nX or nXP), or

    [All/Center/Dynamic/Extents/Previous/Scale/Window/Object] <real time>: _W

    Specify first corner: 13.1961936276982,12.972925917324 Specify opposite corner:

    30.6221132095147,0.482638801189623

    Command: QZW

    Specify first corner:

    Specify opposite corner:

    Command:

    Both versions of the command deliver the results in terms of smooth view transitions, but the quiet version reduces the command-line clutter by a) passing a flag to SendStringToExecute() to stop the command from being echoed and b) setting the NOMUTT system variable to prevent the command "muttering" from being echoed. It uses a simple trick to reset the system variable afterwards by appending "_NOMUTT 0 " to the string to be executed (which we don't see, as NOMUTT is still set to 1 :-). Assuming the ZOOM command terminates correctly, the NOMUTT system variable should be reset: there is a slight risk that something causes ZOOM to fail, at which point it might be worth checking NOMUTT from time to time elsewhere in your app: as mentioned earlier, having NOMUTT set to 1 can be very disconcerting for the user. I should, in fact, probably just force NOMUTT to 0 after the ZOOM (if you check the the above code, you'll see we reset it to the prior value, which is generally a good technique when modifying system variables as they retain the value previously chosen by the user). Anyway - I'll leave the final choice up to you, but do be aware of the risk.

    Update:

    Undo is a problem with this implementation - havin NOMUTT as a separate command leaves it at risk of being undone by the user (leaving the system in a scary, silent state). This post presents an enhanced approach which provides much better support for undo.

    TrackBack

    TrackBack URL for this entry:
    http://www.typepad.com/services/trackback/6a00d83452464869e200e554d76c3b8833

    Listed below are links to weblogs that reference No muttering at the back! Reducing the background noise when sending commands to AutoCAD:

    Comments

    Hi Kean.

    I've been using animated view transitions in the pans and zooms that some of my apps do for quite some time.

    I use the undocumented AcMdgViewTransitionServices class from acmdginternal.dll to temporarily enable animated view transitions if they're not curently enabled for scripts, and then just P/Invoke acedCmd() to run the ZOOM command, which of course, makes no noise (with CMDECHO set to 0).

    I have yet to see a good case for why we should avoid the use of acedCmd() from the document context to entirely avoid the kinds of problems your code solves using NOMOTT.

    Hi Tony,

    I've actually realised that sending a few escape chars should reduce (probably remove) the risk of NOMUTT remaining set. I need to give it a try, but that seems pretty solid.

    Ultimately it's all down to choice, of course. I'd tend to stick to supported (and ideally managed) APIs, where possible, all else being equal.

    Cheers,

    Kean

    Hi Kean,

    a more general question:
    How can I obtain an object which the user selected before entering the command ?

    Mark

    Hi Mark,

    Try this previous post.

    Kean

    Hi Kean,

    thanks for your tip. I still needed a second try to get it working, because I overlooked the CommandFlags.UsePickSet flag.
    Readers might interest that specifying CommandFlags.Redraw leaves the selection intact after execution of the usercommand, if you omit it, the selection is cleared.

    Instead of using some more natural term as SelectedObject AUTOCAD developers chose SelectImplied which is as arcane as the whole object model. Even simple things turn out to need an unnecessary complicate solution.
    Really annoying is, that there is no (good) documentation on all that stuff.
    Autodesk should take care of this instead of producing videos like "10 easy ways to crash your Autocad".

    But fortunately we have you Kean :) Your blog turned out to be the most valuable source for information regarding the .NET interface.

    Keep on and thanks again

    Mark


    "Ultimately it's all down to choice, of course. I'd tend to stick to supported (and ideally managed) APIs, where possible, all else being equal."

    Perhaps, but I don't regard acedCommand() as an unsupported API and ultimately, most of the managed APIs we use are just wrappers that delegate to native code anyway :)

    Couple that with the various other problems many have with SendStringToExecute's asynchronous nature and the fact that it can only accept string input, is what makes acedCommand() a much more robust solution, from my perspective.

    I wasn't actually referring to acedCommand() - I was more talking about the use of acmdginternal.dll being unsupported, although that was only a side point.

    That said, acedCommand() is indeed not supported from .NET. Yes, our managed wrappers call through to unmanaged APIs internally, but you'll notice we haven't wrapped acedCommand(). We support people driving commands by sending them via the command-throat (SendStringToExecute() or COM's SendCommand()).

    I'm not saying you shouldn't do what you're doing, Tony, I'm just sharing a different perspective: while you may consider it less clean to pump commands, from an AutoCAD internals perspective it's actually considered best practice (it removes the possibility of exceeding levels of re-entrancy, for instance and is slightly more future-proof: Microsoft's threat to deprecate fibers will probably impact acedCommand() in some way, at least that's my understanding).

    A big problem I've realised with the above post is related to Undo, but there'll be a post going live on Monday to show a slightly different approach to address that.

    Kean

    Hi Kean,
    Your post is very useful to fresh hands in this field.
    Thank you
    Geospatial Utilities

    I am using visual studio 2005 and RealDWG 2009 and I trying to open the dwg file using realdwg library in c# application and following is my source code.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using Autodesk.AutoCAD.DatabaseServices;
    namespace RealDWGTest
    {
    public partial class Form1 : Form
    {
    public Form1()
    {
    InitializeComponent();
    InitializeRealDwgComponents(); // This line shows the error
    }

    private void InitializeRealDwgComponents()
    {
    Autodesk.AutoCAD.Runtime.RuntimeSystem.Initialize(new RealDWGApp(), 1033);
    Database db = new Database();
    db.ReadDwgFile("C:\test.dwg", FileOpenMode.OpenForReadAndAllShare, true, "");
    }
    }

    public class RealDWGApp : Autodesk.AutoCAD.DatabaseServices.HostApplicationServices
    {
    public override string FindFile(string fileName, Database database, FindFileHint hint)
    {
    throw new Exception("The method or operation is not implemented.");
    }
    }
    }


    When I try to run this program following error occurred:
    "Could not load file or assembly 'acdbmgd, Version=17.2.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified."


    Please help to solve this problem.

    Santosh -

    This question is off-topic. Please submit it via the ADN website, if you're a member.

    Otherwise you might have some luck at the AutoCAD .NET Customization Discussion Group - I don't believe there's a group dedicated to RealDWG, unfortunately.

    Regards,

    Kean

    Verify your Comment

    Previewing your Comment

    This is only a preview. Your comment has not yet been posted.

    Working...
    Your comment could not be posted. Error type:
    Your comment has been posted. Post another comment

    The letters and numbers you entered did not match the image. Please try again.

    As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

    Having trouble reading this image? View an alternate.

    Working...

    Post a comment

    Feed & Share

    Search