Author Topic: Expose ARX Function to .NET  (Read 31559 times)

0 Members and 1 Guest are viewing this topic.

Krasyn

  • Guest
Re: Expose ARX Function to .NET
« Reply #120 on: September 02, 2008, 01:27:44 AM »
PyAcadNet allows me to use all COM functionality and use autocad.exe as an in-process server like vba does. It would be fine if net.api worked too not only a subset. 
Are you saying only a subset of the .NET API works in PyAcad.NET? If so please explain what you can't do? If it's registering native AutoCAD commands that's a known issue. I'd be interested in knowing of anything else.
I translated a couple of programs from C# to Python and it was OK. The third program used reactors and it failed to work.

MickD

  • King Gator
  • Posts: 3520
  • (x-in)->[process]->(y-out)
Re: Expose ARX Function to .NET
« Reply #121 on: September 03, 2008, 11:12:53 PM »
A little bit further down the track, almost there, here's where I will need someone to code up a simple script to load a command, we can then fill in the missing function and do some testing.

Can we call a script that has 'main' that can execute the loading or can we nominate a method name that we know we can hard code into this dll that calls the main script's load method??

Delegates between IP and C# aren't a problem though (so I've read), it will be interesting to see if this flys :)

Code: [Select]
using System;
using System.Collections.Generic;
using System.Text;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

using IronPython.Hosting;
using IronPython.Compiler;
using PyAcadNET.PyAcadNET;

using acadapp = Autodesk.AutoCAD.ApplicationServices.Application;

// the one and only callback to be called by acad to
// execute our ironpython methods in our scripts.
public delegate void callback();
public delegate void maincallback();

namespace IpyEmbed1
{
    class CmdStack
    {
        private maincallback mcb;
        public CmdStack()
        {
            // init our stack object:
            mcb = new maincallback(RunPyMethod);
        }
        // our dictionary object to hold the command name
        // as key with script method delegate to execute.
        // these are used to call the method in the IPy script
        private static Dictionary<string, callback> cmd_dict;
       
        // Adds a command name and method def name to the cmd_stack
        public void Push(string cmd_string, string cmd_def, PythonEngine pyeng, EngineModule mod)
        {
            // create the callback:
            callback cb = pyeng.CreateMethod<callback>(cmd_def + "()", mod);
            // add the command string (used in acad) and the
            // callback to the script method to the cmd_stack dictionary:
            cmd_dict.Add(cmd_string, cb);

            // now add the command to the acad cmd stack:
            PyCommands.load_command("PYCOMMANDS", cmd_string, cmd_string, (int)CommandFlags.Modal, mcb);
        }

        // This function is the same function that gets called from every
        // acad command (maincallback), in it we grab the current command
        // string and use it to get the function name from the dictionary
        // and call into the python script to execute the dict's callback value.
        public static void RunPyMethod()
        {
            // grab the string for the current command in action: (the return needs checking!)
            string cmdstr = acadapp.DocumentManager.MdiActiveDocument.CommandInProgress;
            // get the function name from the dictionary using the cmd name as the key:
            callback cb = cmd_dict[cmdstr];         
            // call it:
            cb();
        }
    }
    class App : IExtensionApplication
    {
        private CmdStack cmd_stack;
        private PythonEngine engine;
        EngineModule pymod;
        public void Initialize()
        {
            cmd_stack = new CmdStack();
            engine = new PythonEngine();
            engine.AddToPath(Environment.CurrentDirectory);
            pymod = engine.CreateModule("pyacadnet", true);
            pymod.Globals["cmd_stack"] = cmd_stack; 

            // call our function to initialise our commands:
            InitPyCmds();
        }

        // a method to call into our main IP script that calls a method
        // to load all our commands from IP
        void InitPyCmds()
        {
            // use the engine and execute/evaluate the main script, in this script
            // there must be a method for loading commands, this is th scripts
            // 'entry point' just like you have with a dll. To make this work
            // we need to have a 'main' or setup script that's always available
            // and in it is the one and only 'load_command_methods' method
            // or similar.
            // This will need some thorough error checking for files etc.!!
        }
        public void Terminate()
        {
            // pass
        }

    }
}

Solution attached for those that want to join in.
Forth is like the Tao: it is a Way, and is realized when followed.
Its fragility is its strength; its simplicity is its direction - Michael Ham

"The E in Javascript stands for 'easy'." Florin Pop tweet

MickD

  • King Gator
  • Posts: 3520
  • (x-in)->[process]->(y-out)
Re: Expose ARX Function to .NET
« Reply #122 on: November 13, 2008, 08:48:29 PM »
I stumbled on to a bit more info, the help doc's are a bit vague on how things work (unless you thoroughly understand the inner workings of IP) but we can give this a try, it compiled ok anyway :).

Code: [Select]
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

using IronPython.Hosting;
using IronPython.Compiler;
using PyAcadNET.PyAcadNET;

using acadapp = Autodesk.AutoCAD.ApplicationServices.Application;

// the one and only callback to be called by acad to
// execute our ironpython methods in our scripts.
public delegate void callback();
public delegate void maincallback();

namespace IpyEmbed1
{
    class CmdStack
    {
        private maincallback mcb;
        public CmdStack()
        {
            // init our stack object:
            mcb = new maincallback(RunPyMethod);
        }
        // our dictionary object to hold the command name
        // as key with script method delegate to execute.
        // these are used to call the method in the IPy script
        private static Dictionary<string, callback> cmd_dict;

        // Adds a command name and method def name to the cmd_stack
        public void Push(string cmd_string, PythonEngine pyeng, EngineModule mod)
        {
            // create the callback:
            callback cb = pyeng.CreateMethod<callback>(cmd_string + "()", mod);
            // add the command string (used in acad) and the
            // callback to the script method to the cmd_stack dictionary:
            cmd_dict.Add(cmd_string, cb);

            // now add the command to the acad cmd stack:
            PyCommands.load_command("PYCOMMANDS", cmd_string, cmd_string, (int)CommandFlags.Modal, mcb);
        }

        // This function is the same function that gets called from every
        // acad command (maincallback), in it we grab the current command
        // string and use it to get the function name from the dictionary
        // and call into the python script to execute the dict's callback value.
        public static void RunPyMethod()
        {
            // grab the string for the current command in action: (the return needs checking!)
            string cmdstr = acadapp.DocumentManager.MdiActiveDocument.CommandInProgress;
            // get the function name from the dictionary using the cmd name as the key:
            callback cb = cmd_dict[cmdstr];
            // call it:
            cb();
        }
    }

    public delegate void callback();
    class App : IExtensionApplication
    {
        private CmdStack cmds;
        private PythonEngine engine;
        private EngineModule mod;
        public void Initialize()
        {
            // create the engine and pre-compile our script for better first run perf.
            engine = new PythonEngine();
            mod = engine.CreateModule();
            engine.AddToPath(Environment.CurrentDirectory);
            string stmts = LoadScriptFile("pyacad.py");
            engine.Compile(stmts);

            // here we just hard code some test commands, later we can ask the script
            // to call back here and do this dynamically.
            cmds = new CmdStack();
            cmds.Push("py1",engine, mod);
            cmds.Push("py2", engine, mod);
            cmds.Push("py3", engine, mod);
        }

        public static string LoadScriptFile(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentException("Argument cannot be a null or empty string.", "fileName");

            string fullScriptPath = null;
            string result;

            fullScriptPath = string.Format(@"{0}\{1}", Environment.CurrentDirectory, fileName);

            using (StreamReader reader = File.OpenText(fullScriptPath))
            {
                result = reader.ReadToEnd();
                reader.Close();
            }

            return result;
        }
        public void Terminate()
        {
            // pass
        }
    }
}

I don't have time now as lunch is over but if someone can write a simple test script with method def's of "py1", "py2" and "py3", that should be enough to see if it will work. The commands only need to do something simple like print to the command line.
Forth is like the Tao: it is a Way, and is realized when followed.
Its fragility is its strength; its simplicity is its direction - Michael Ham

"The E in Javascript stands for 'easy'." Florin Pop tweet

tjr

  • Guest
Re: Expose ARX Function to .NET
« Reply #123 on: November 13, 2008, 10:52:15 PM »
Mick:

Very cool if it works. Sadly can't test it out tonight. It's 10:48pm here and I am busily fixing bugs in an a C# app that is going live Monday morning. If my app doesn't perform as planed it kind of fails our entire ERP system. :(

I'll try to test it out this weekend.

MickD

  • King Gator
  • Posts: 3520
  • (x-in)->[process]->(y-out)
Re: Expose ARX Function to .NET
« Reply #124 on: November 18, 2008, 01:43:54 AM »
I did a quick test but didn't have any luck and I couldn't get debugging to work properly and ran out of time.

Here's a breif explanation of what I 'think' is happening.

When you compile/execute (compile I understand, execute could have had a better name though), the script is passed and compiled/loaded into the engine, this means all you need to do is call the method in the engine and it all should work.
To make it dynamic, the trick is to know what the method names are in advance and that way you can call the engine directly with the method name.
If I get some more time I'll check it out, I may be able to loop through the engine (I think you can as it stores them in a dictionary) and retrieve all the method names and use those as the registered command names and call straight into the engine to execute them, this means we can ditch the callback system alltogether.

That's the plan anyway :)
Forth is like the Tao: it is a Way, and is realized when followed.
Its fragility is its strength; its simplicity is its direction - Michael Ham

"The E in Javascript stands for 'easy'." Florin Pop tweet

TonyT

  • Guest
Re: Expose ARX Function to .NET
« Reply #125 on: November 26, 2008, 11:53:16 PM »
I did a quick test but didn't have any luck and I couldn't get debugging to work properly and ran out of time.

Here's a breif explanation of what I 'think' is happening.

When you compile/execute (compile I understand, execute could have had a better name though), the script is passed and compiled/loaded into the engine, this means all you need to do is call the method in the engine and it all should work.
To make it dynamic, the trick is to know what the method names are in advance and that way you can call the engine directly with the method name.
If I get some more time I'll check it out, I may be able to loop through the engine (I think you can as it stores them in a dictionary) and retrieve all the method names and use those as the registered command names and call straight into the engine to execute them, this means we can ditch the callback system alltogether.

That's the plan anyway :)

If you can target AutoCAD 2009 or later, there is now a way to add and
remove registered commands dynamically (the Action Recorder uses it to
register recorded actions as commands) without the need for C++/CLI.

Here's a wrapper:

Code: [Select]

// AutoCADCommand.cs  Copyright (c)2008 Tony Tanzillo / www.caddzone.com

using System;
using System.Collections.Generic;
using System.Text;
using Autodesk.AutoCAD.Internal; // <------ acmgdinternal.dll, A2K9 or later only
using Autodesk.AutoCAD.Runtime;
using System.Reflection;

/// This code requires AutoCAD 2009 or later.

namespace CaddZone.AutoCAD.ApplicationServices
{
   /// <summary>
   ///
   /// AutoCADCommand class
   ///
   /// A class that implements a registered AutoCAD
   /// command.
   ///
   /// When an instance of this type is created, the
   /// command is registered with the specified name,
   /// and becomes available to the user.
   ///
   /// When the instance is Disposed, the command is
   /// unregistered and no longer available to the user.
   ///
   /// When the user issues the command, the OnInvoke()
   /// method is invoked and the Invoke event is fired.
   ///
   /// You can handle the command invocation in one of
   /// two ways:
   ///
   ///    - Create an instance of AutoCADCommand, and
   ///      handle the Invoke event.
   ///
   ///    - Derive a type from AutoCADCommand, and override
   ///      the OnInvoke() method.
   ///
   /// This code depends on undocumented/unsupported APIs
   /// from acmdginternal.dll, and requires AutoCAD 2009
   /// or later.
   ///
   /// </summary>

   public class AutoCADCommand : IDisposable
   {
      private string groupName = Assembly.GetCallingAssembly().GetName().Name + "_Commands";
      private string name = string.Empty;
      private CommandFlags flags = CommandFlags.Modal;

      public AutoCADCommand( string Name, string GroupName, CommandFlags flags )
      {
         if( string.IsNullOrEmpty( Name ) )
            throw new ArgumentNullException( "Name" );
         if( !string.IsNullOrEmpty( GroupName ) )
            this.groupName = GroupName;
         this.name = Name;
         this.flags = flags;
         Utils.AddCommand( this.groupName, this.name, this.name, this.flags, this.invoke );
      }

      public AutoCADCommand( string Name, CommandFlags flags )
         : this( Name, null, flags )
      {
      }

      public AutoCADCommand( string Name )
         : this( Name, null, CommandFlags.Modal )
      {
      }

      ~AutoCADCommand()
      {
         if( !disposed )
            Dispose( false );
      }

      public string GroupName
      {
         get
         {
            return groupName;
         }
      }

      public string Name
      {
         get
         {
            return name;
         }
      }
     
      private void invoke()
      {
         OnInvoke();
      }

      protected virtual void OnInvoke()
      {
         if( this.Invoke != null )
            this.Invoke( this, EventArgs.Empty );
      }

      public event EventHandler Invoke = null;

      private bool disposed = false;

      protected virtual void Dispose( bool disposing )
      {
         disposed = true;
         if( disposing )
         {
            Utils.RemoveCommand( this.GroupName, this.Name );
            GC.SuppressFinalize( this );
         }
      }

      public void Dispose()
      {
         if( !disposed )
            Dispose( true );
      }
   }
}

// AutoCADCommand.cs