dm脚本中如何导入其他源码文件

How to import other source code files in dm script

有没有办法在dm-script中使用多个代码文件来构造代码?类似于:

import "path/to/utility_functions.s";

utility_functions.do_something_general();

请注意,如果可能的话,我不想将代码作为菜单项。该代码仅包含我在主脚本中使用的函数。


我尝试了以下方法:

文件 1:test.s

void test(){
    result("test\n");
}

文件 2:要求-test.s

AddScriptFileToPackage("path/to/test.s", "test", 3, "test-function", "", "", 1);

ExecuteScriptString("test()"); // works immediately but feels wrong
test(); // works after restart

现在我遇到了以下问题:

我觉得我在某些地方没有做错。

DM脚本不支持按需加载包,但是有两种不同的方式可以实现你想要的:

使用库包

When you "install" a script, you can choose to either install it as menu-command or as a library. This is true for both installing scripts via the menu command (which get stored in the global preferences file) or via the scripting-command (which can be used to create .gtk files which one can then add/remove from the plugins folder as needed).

The "menu" option will install a script such that it is invoked once via the menu-item but does not stay in memory otherwise.

The "library" option will execute a script once on startup and keep the script itself in scope. So you can define methods (or classes) in a library file and have it generally available. And you can put some executable code in a library if you need some startup-action.

Using script libraries as .gtk plugins is possibly the recommended way to achieve what you want. They are, however, always loaded.

一条建议:如果你制作库确保你使用 非常 独特的 class 和方法名称以避免任何冲突。我建议用一些库名称预先修复所有 class/method 名称,即使用 MyLib_MyClass 而不是 MyClass 等。

澄清:作为库包添加的脚本是永久添加到软件中,即这些包被创建一次,然后被放置在插件文件夹中。它们将始终在 DM 启动时加载并可用。 Library 封装方式暂时不适合'loading' 外部脚本。 DM 脚本不支持这样的 'on demand import'。

创建包的脚本命令是实用命令,可帮助您以一种简单易管理的方式创建包。通常,人们会创建一个 "Create package XY" 脚本,其中包含几个这样的命令,将所有脚本从一个位置添加到一个包中。该脚本将被调用一次以创建包文件(之后它已经在插件文件夹中。) 只有当包含的脚本发生变化并且因此需要更新包时,才会再次调用 create-package 脚本。请注意,在这种情况下,首先需要从插件文件夹中删除包文件并在不加载它的情况下启动 DigitalMicrograph,以便创建 new 包。否则脚本将 append 到包,如果包中已经存在同名方法,则不可能。

F1 帮助文档有一个示例脚本:


典型例子,使用GMS 3.4.0:

Script stored at: C:\Tmp\testLib.s

void TestCall()
{
  Result("\nTest")
}

Script stored at: C:\Tmp\menuAction.s

Result("\nPerforming an action here.")

One-time run script to install a package:

// General package parameters
// *********************************************
string pkNa = "myPkg"       // Filename of plugin
number pkLe = 3             // level 3 (.gtk) only needed for load order
string pkLo = "user_plugin" // plugin location

string scriptRoot = "C:\Temp\"        

// List of Scripts to be installed as menu items
// *********************************************
// Each entry needs a (unique) command-name, a menu-name and an optional sub-menu name. 
// The "isLibary" flag is set to 0
// It is possible to add the same script multiple times. The script will be executed when the menu item
// is chosen. Methods and Classes of the script are not available otherwise
// A separator can be added by installing and empty script with a (unique) command name starting with "-"
AddScriptFileToPackage( scriptRoot + "menuAction.s", pkNa, pkLe, pkLo, "Call 1", "MyMenu", "MySubMenu", 0 ) 
AddScriptFileToPackage( scriptRoot + "menuAction.s", pkNa, pkLe, pkLo, "Call 2", "MyMenu", "", 0 ) 
AddScriptToPackage( "", pkNa, pkLe, pkLo, "-sep1", "MyMenu", "", 0 ) 
AddScriptFileToPackage( scriptRoot + "menuAction.s", pkNa, pkLe, pkLo, "Call 3", "MyMenu", "", 0 ) 

// List of Scripts to be installed as library 
// *********************************************
// Each entry needs a (unique) command-name. Menu-name and sub-menu name are "". 
// The "isLibary" flag is set to 1
// The script will be executed once on startup (if there is executable code). It is also executed once
// here during the install.
// Methods and Classes of the script are permanently available and need unique names.
// Adding a script to the package as libary can be used to create on-load-version info output.
AddScriptFileToPackage( scriptRoot + "testLib.s", pkNa, pkLe, pkLo, "library-1", "", "", 1 ) 
AddScriptToPackage( "Result(\"Script packages myPkg loaded.\n\")", pkNa, pkLe, pkLo, "myPkg-versionInfo", "", "", 1 )

安装脚本 运行 后会有:

  • 这样的菜单:
  • 在结果window中输出如下:
  • 文件夹C:\Users\USERNAME\AppData\Local\Gatan\Plugins\myPkg.gtk
  • 中的一个包文件
  • 脚本命令TestCall()普遍适用于所有脚本。

只要 .gtk 文件保留在插件文件夹中,每次 DM 启动时都会加载该包。

从脚本中调用脚本代码

The scripting language supports two commands to call a script from within a script:

  • Number ExecuteScriptString( String text )

  • Number ExecuteScriptFile( String file_path )

Using the command to execute scripts form disc can do what you want, but maintaining a useful 'library' that way could be tedious. It also does not allow you to install classes.


从脚本中调用脚本的示例:

// Direct example
void Demo()
{
    ClearResults()
    Result( "I am a test call.\n")
    number n = 5
    Result( "I am working on the number: " + n )
}
Demo()

//Having the script as a string
number otherNumber = 11 // To show how you can modify a script call as an example
string scriptStr
scriptStr += "void Demo()\n{" + "\n"
scriptStr += "ClearResults()" + "\n"
scriptStr += "Result( \"I am a test call.\n\")" + "\n"
scriptStr += "number n = " + otherNumber + "\n"
scriptStr += "Result( \"I am working on the number: \" + n )"+ "\n"
scriptStr += "}\n"
scriptStr += "Demo()\n"

If ( TwoButtonDialog("Script-call","Show it", "Run it") )
{
    ClearResults()
    Result( scriptStr ) 
}
else
    ExecuteScriptString( scriptStr )

以下构建脚本用法的明确示例可能更接近您正在寻找的内容。它表明,在单个 DM 会话过程中,可以编辑模块源文件并重复重建包,而无需重新启动 DM,这与 BmyGuest 的答案中提供的关于包创建的说明相反。此示例还使用了非常方便的 GetCurrentScriptSourceFilePath 函数,当可以在同一文件夹中找到构建脚本和模块源文件时,该函数大大简化了文件路径引用(这是我在自己的开发项目中采用的方法)。

下面是我的文件在这个例子中的排列方式:

这两个源模块都是非常简单的函数和class库。

这是模块 1:

    void Module1SayHello()
    {
        OKDialog("Hello from module 1");
    }

这是模块 2:

    class Module2TestClass
    {
        void Module2SayHello(Object self)
        {
            OKDialog("Hello from module 2");
        }
    }

这是构建脚本:

    void main()
    {
        // Establish the source code directory relative to the current build script location
        String buildScriptSourceFilePath;
        GetCurrentScriptSourceFilePath(buildScriptSourceFilePath);
        String sourceFileDir = buildScriptSourceFilePath.PathExtractDirectory(0);

        // Add the modules
        AddScriptFileToPackage(sourceFileDir.PathConcatenate("Module1.s"), "MultiModuleTest", 3, "Module1", "", "", 1);
        AddScriptFileToPackage(sourceFileDir.PathConcatenate("Module2.s"), "MultiModuleTest", 3, "Module2", "", "", 1);
    }

    main();

与上述说明相反,此构建脚本可以在 DM 会话期间 运行 多次,并且每次都会替换包文件的内容。所以现在人们有了一个非常好的开发环境,可以在其中打开模块的源文件,根据需要对其进行编辑,保存,然后重建包文件。可以使用以下测试脚本来查看行为在编辑、保存和重建模块源文件中任何函数或方法的实现时发生的变化:

    void main()
    {
        Module1SayHello();
        Alloc(Module2TestClass).Module2SayHello();
    }

    main();

由于 DM 脚本解释器解析、标记化和执行代码的方式,脚本中任何地方调用的所有函数和方法必须在执行脚本之前预先定义。这就是为什么不能简单地将上述测试脚本或使用添加模块的任何其他脚本附加到构建脚本末尾的原因(除非嵌入到传递给 ExecuteScriptString 函数的字符串中,如提出的问题中所指出的) .因此,导入代码模块的概念(例如 Python 中)在 DM 脚本中是不可能的(正如 BmyGuest 在对答案的评论中指出的那样)。从这个意义上说,DM 脚本显示其根源于 1990 年代的编码概念,这些概念通常涉及单独的编译、链接和执行阶段。

尽管如此,此处描述的构建脚本方法允许人们利用真正的集成开发环境 (IDE) 的功能。例如,可以将模块源文件(和构建脚本)添加到 Visual Studio 中的项目,并获得现代多文件代码编辑器和修订控制的所有好处(例如通过 Git)。这就是我使用 Enabler 框架所做的。

需要注意的是,一旦 DM 会话关闭,插件(程序包)文件就会以某种方式最终确定,因此在未来的 DM 会话中它不能再被构建脚本替换。在这种情况下,必须先从插件文件夹中删除包文件,然后才能在 DM 中恢复另一个开发会话(如 BmyGuest 的说明中所述)。

对于其他需要这个的人,我现在正在使用 AddScriptFileToPackage(),受到@BmyGuest 和@MikeKundmann 的启发。

以下 main.s 在我的 GMS 中始终打开。我正在处理的真实代码在 program.s 中。要测试您的代码 执行 main.s。此文件可以在一个会话中执行多次!

为了打开 GMS,我使用下面的 (Windows) 批处理文件。这会自动删除已注册的插件,使 main.s 再次可用。为了调试,我创建了一个 python 脚本,它结合了 main.s 中列出的所有文件。这样 GMS 就会跳转到错误。这个python程序可以是downloaded from my github page.

/**
 * File: main.s
 */

String __file__;
GetCurrentScriptSourceFilePath(__file__);
String __base__ = __file__.PathExtractDirectory(0);

/**
 * Load and add the file `filename`, the name will be the `filename` without
 * the extension.
 *
 * This is dynamic only for the current session. If GMS is restarted, using 
 * this will create errors except if the plugins folder does not contain the 
 * required files (delete `%LOCALAPPDATA%\Gatan\Plugins\` before starting).
 *
 * @param filename The filename (or path) relative to the path of this file
 * @param name The internal name to register the script with
 */
void require(String filename, String name){
    // AddScriptFileToPackage(
    //    <file_path>, 
    //    <packageName: filename of .gtk file in plugins>, 
    //    <packageLevel: load order [0..3]>,
    //    <command_name: id/name of the libary/command>,
    //    <menu_name: name of the menu, ignored if isLibrary=1>
    //    <sub_menu_name: name of the submenu, ignored if isLibrary=1>,
    //    <isLibrary: wheter to add as library (1) or as menu item (0)>
    // )
    AddScriptFileToPackage(__base__.PathConcatenate(filename), "__require_main_" + name, 3, name, "", "", 1);
}

/**
 * Require the file `filename` with the basename of the `filename` as the name.
 *
 * @see require(String filename, String name);
 *
 * @param filename The filename (or path) relative to the path of this file
 */
void require(String filename){
    require(filename, PathExtractBaseName(filename, 0));
}

void main(){
    // add libaries
    require("string-lib.s");

    // add main file
    require("program.s");
}

main();

用于启动 GMS 的 (Windows) 批处理文件。这会自动删除插件文件夹。那么main.s不会造成任何问题。

@echo off

rem
rem File: start-gatan.bat
rem ---------------------

echo Deleting GMS cached libaries...

SET plugins_path=%LOCALAPPDATA%\Gatan\Plugins\
SET gms_path=%PROGRAMFILES%\Gatan\DigitalMicrograph.exe

if exist %plugins_path% (
    echo Deleting all .gtk files in %plugins_path%...
    del %plugins_path%__require_main_*.gtk /F /Q
    del %plugins_path%__require_main_*.gt1 /F /Q
    del %plugins_path%__require_main_*.gt2 /F /Q
    del %plugins_path%__require_main_*.gt3 /F /Q

    if exist "%gms_path%" (
        echo Starting GMS
        start "" "%gms_path%"
    ) else (
        echo GMS path %gms_path% does not exist.
        pause
    )
) else (
    echo Plugins path %plugins_path% does not exist.
    pause
)