using System; using System.Linq; using System.Text; using IronPython.Runtime.Exceptions; using Microsoft.Scripting; using Microsoft.Scripting.Hosting; using System.Collections.Generic; using Autodesk.Navisworks.Api; using Autodesk.Navisworks.Api.Clash; using Autodesk.Navisworks.Api.Timeliner; namespace NavisPythonShell.NpsRuntime { /// /// Executes a script scripts /// public class ScriptExecutor { private string _message; private readonly IRpsConfig _config; public ScriptExecutor(IRpsConfig config) { _config = config; _message = ""; } public string Message { get { return _message; } } /// /// Run the script and print the output to a new output window. /// public int ExecuteScript(string source, string sourcePath) { try { var engine = CreateEngine(); var scope = SetupEnvironment(engine); var scriptOutput = new ScriptOutput(); scriptOutput.Show(); var outputStream = new ScriptOutputStream(scriptOutput, engine); scope.SetVariable("__window__", scriptOutput); scope.SetVariable("__file__", sourcePath); // Add script directory address to sys search paths var path = engine.GetSearchPaths(); path.Add(System.IO.Path.GetDirectoryName(sourcePath)); engine.SetSearchPaths(path); engine.Runtime.IO.SetOutput(outputStream, Encoding.UTF8); engine.Runtime.IO.SetErrorOutput(outputStream, Encoding.UTF8); engine.Runtime.IO.SetInput(outputStream, Encoding.UTF8); var script = engine.CreateScriptSourceFromString(source, SourceCodeKind.Statements); var errors = new ErrorReporter(); var command = script.Compile(errors); if (command == null) { // compilation failed _message = string.Join("\n", errors.Errors); return -1; } try { script.Execute(scope); _message = (scope.GetVariable("__message__") ?? "").ToString(); return (int)(scope.GetVariable("__result__") ?? 0); } catch (SystemExitException) { // ok, so the system exited. That was bound to happen... return 0; } catch (Exception exception) { // show (power) user everything! _message = exception.ToString(); return -1; } } catch (Exception ex) { _message = ex.ToString(); return -1; } } private ScriptEngine CreateEngine() { var engine = IronPython.Hosting.Python.CreateEngine(new Dictionary() { { "Frames", true }, { "FullFrames", true } }); return engine; } private void AddEmbeddedLib(ScriptEngine engine) { // use embedded python lib var asm = this.GetType().Assembly; var resQuery = from name in asm.GetManifestResourceNames() where name.ToLowerInvariant().EndsWith("python_27_lib.zip") select name; var resName = resQuery.Single(); var importer = new IronPython.Modules.ResourceMetaPathImporter(asm, resName); dynamic sys = IronPython.Hosting.Python.GetSysModule(engine); sys.meta_path.append(importer); } /// /// Set up an IronPython environment - for interactive shell or for canned scripts /// public ScriptScope SetupEnvironment(ScriptEngine engine) { var scope = IronPython.Hosting.Python.CreateModule(engine, "__main__"); SetupEnvironment(engine, scope); return scope; } public void SetupEnvironment(ScriptEngine engine, ScriptScope scope) { // these variables refer to the signature of the IExternalCommand.Execute method scope.SetVariable("__message__", _message); scope.SetVariable("__result__", 0); // add two special variables: __revit__ and __vars__ to be globally visible everywhere: var builtin = IronPython.Hosting.Python.GetBuiltinModule(engine); builtin.SetVariable("__vars__", _config.GetVariables()); // add the search paths AddSearchPaths(engine); AddEmbeddedLib(engine); // reference Navisworks Api Document and Application engine.Runtime.LoadAssembly(typeof(Autodesk.Navisworks.Api.Document).Assembly); engine.Runtime.LoadAssembly(typeof(Autodesk.Navisworks.Api.Clash.ClashTest).Assembly); engine.Runtime.LoadAssembly(typeof(Autodesk.Navisworks.Api.Timeliner.DocumentTimeliner).Assembly); // also, allow access to the RPS internals engine.Runtime.LoadAssembly(typeof(NavisPythonShell.NpsRuntime.ScriptExecutor).Assembly); } /// /// Be nasty and reach into the ScriptScope to get at its private '_scope' member, /// since the accessor 'ScriptScope.Scope' was defined 'internal'. /// private Microsoft.Scripting.Runtime.Scope GetScope(ScriptScope scriptScope) { var field = scriptScope.GetType().GetField( "_scope", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); return (Microsoft.Scripting.Runtime.Scope)field.GetValue(scriptScope); } /// /// Add the search paths defined in the ini file to the engine. /// The data folder (%APPDATA%/NavisPythonShell20XX) is also added /// private void AddSearchPaths(ScriptEngine engine) { var searchPaths = engine.GetSearchPaths(); foreach (var path in _config.GetSearchPaths()) { searchPaths.Add(path); } engine.SetSearchPaths(searchPaths); } } public class ErrorReporter : ErrorListener { public List Errors = new List(); public override void ErrorReported(ScriptSource source, string message, SourceSpan span, int errorCode, Severity severity) { Errors.Add(string.Format("{0} (line {1})", message, span.Start.Line)); } public int Count { get { return Errors.Count; } } } }