diff --git a/CHANGELOG b/CHANGELOG index 05683e6..3de1ae3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +Version 1.0.3 +============= + +- Adding `__version__` to module +- Adding VERSION file +- Adding `separate_thread` option to allow execution of dm-script in a separate dm-script thread + Version 1.0.2 ============= diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..1cc5f65 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.1.0 \ No newline at end of file diff --git a/example/example_separate_thread.py b/example/example_separate_thread.py new file mode 100644 index 0000000..cb3a2b9 --- /dev/null +++ b/example/example_separate_thread.py @@ -0,0 +1,148 @@ +"""This is an example file of how to use the `execdmscript` module.""" + +# = = = = = = = = = = = = = = IGNORE START = = = = = = = = = = = = = = = = = = +# Ignore the following code until the second line +# +# This code is for getting the __file__. GMS does not set the __file__ variable +# on running scripts. That causes the example file not to run properly except +# the execdmscript module is in one of the GMS plugin directories. +# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +try: + import DigitalMicrograph as DM + in_digital_micrograph = True +except ImportError: + in_digital_micrograph = False + +file_is_missing = False +try: + if __file__ == "" or __file__ == None: + file_is_missing = True +except NameError: + file_is_missing = True + +if in_digital_micrograph and file_is_missing: + # the name of the tag is used, this is deleted so it shouldn't matter anyway + file_tag_name = "__python__file__" + # the dm-script to execute, double curly brackets are used because of the + # python format function + script = ("\n".join(( + "DocumentWindow win = GetDocumentWindow(0);", + "if(win.WindowIsvalid()){{", + "if(win.WindowIsLinkedToFile()){{", + "TagGroup tg = GetPersistentTagGroup();", + "if(!tg.TagGroupDoesTagExist(\"{tag_name}\")){{", + "number index = tg.TagGroupCreateNewLabeledTag(\"{tag_name}\");", + "tg.TagGroupSetIndexedTagAsString(index, win.WindowGetCurrentFile());", + "}}", + "else{{", + "tg.TagGroupSetTagAsString(\"{tag_name}\", win.WindowGetCurrentFile());", + "}}", + "}}", + "}}" + ))).format(tag_name=file_tag_name) + + # execute the dm script + DM.ExecuteScriptString(script) + + # read from the global tags to get the value to the python script + global_tags = DM.GetPersistentTagGroup() + if global_tags.IsValid(): + s, __file__ = global_tags.GetTagAsString(file_tag_name); + if s: + # delete the created tag again + DM.ExecuteScriptString( + "GetPersistentTagGroup()." + + "TagGroupDeleteTagWithLabel(\"{}\");".format(file_tag_name) + ) + else: + del __file__ + + try: + __file__ + except NameError: + # set a default if the __file__ could not be received + __file__ = "" + +# = = = = = = = = = = = = = = = IGNORE END = = = = = = = = = = = = = = = = = = + +# The start of the example file + +import os +import sys +import time +import threading + +import DigitalMicrograph as DM + +if __file__ != "": + # add the parent directory to the system path so the execdmscript file + # can be imported + base_path = str(os.path.dirname(os.path.dirname(__file__))) + + if base_path not in sys.path: + sys.path.insert(0, base_path) + +from execdmscript import exec_dmscript + +try: + def do_something(): + for i in range(100): + i += 1 + print("Progress: {}".format(i)) + DM.GetPersistentTagGroup().SetTagAsLong("__progress", i); + time.sleep(0.05) + + thread = threading.Thread(target=do_something) + thread.start() + + dialog_script = """ + number update_task; + class ProgressDialog : UIFrame{ + void updateDialog(object self){ + number progress; + if(GetPersistentTagGroup().TagGroupGetTagAsLong("__progress", progress)){ + self.DLGSetProgress("progress_bar", progress / 100); + self.validateView(); + } + } + + object init(object self){ + TagGroup Dialog = DLGCreateDialog("Dialog"); + + TagGroup progress_bar = DLGCreateProgressBar("progress_bar"); + progress_bar.DLGInternalpadding(150, 0); + + Dialog.DLGAddElement(progress_bar); + update_task = AddMainThreadPeriodicTask(self, "updateDialog", 0.1); + + self.super.init(Dialog); + return self; + } + } + // do not move this in the thread part, this will not work anymore + object progress_dlg = alloc(ProgressDialog).init(); + + """ + exec_script = """ + if(!GetPersistentTagGroup().TagGroupDoesTagExist("__progress")){ + GetPersistentTagGroup().TagGroupCreateNewLabeledTag("__progress"); + GetPersistentTagGroup().TagGroupSetTagAsLong("__progress", 0); + } + + progress_dlg.pose(); + + if(GetPersistentTagGroup().TagGroupDoesTagExist("__progress")){ + GetPersistentTagGroup().TagGroupDeleteTagWithLabel("__progress"); + } + RemoveMainThreadTask(update_task); + """ + with exec_dmscript(dialog_script, separate_thread=(exec_script, ), debug=False) as script: + pass + + thread.join() +except Exception as e: + # dm-script error messages are very bad, use this for getting the error text and the + # correct traceback + print("Exception: ", e) + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/execdmscript/__init__.py b/execdmscript/__init__.py index e9855a8..0e89355 100644 --- a/execdmscript/__init__.py +++ b/execdmscript/__init__.py @@ -34,4 +34,6 @@ from .execdmscript import exec_dmscript from .execdmscript import get_dm_type from .execdmscript import get_python_type -from .execdmscript import DMScriptWrapper \ No newline at end of file +from .execdmscript import DMScriptWrapper + +__version__ = "1.1.0" \ No newline at end of file diff --git a/execdmscript/execdmscript.py b/execdmscript/execdmscript.py index 573b413..4d63451 100644 --- a/execdmscript/execdmscript.py +++ b/execdmscript/execdmscript.py @@ -583,21 +583,18 @@ def getExecDMScriptCode(self) -> str: dmscript, code, source, script, startpos ) - # the code for the readvars - code = self.getSyncDMCode() - dmscript, startpos = self._addCode( - dmscript, code, "", self.getSyncDMCode, startpos - ) - + wait_for_signals = [] # execute in a separate thread if isinstance(self.separate_thread, collections.Sequence): - code = self.getSeparateThreadStartCode() - dmscript, startpos = self._addCode( - dmscript, code, "", - self.getSeparateThreadStartCode, startpos - ) - for i, (kind, script) in enumerate(DMScriptWrapper.normalizeScripts(self.separate_thread)): + wait_for_signals.append(i) + + code = self.getSeparateThreadStartCode(i) + dmscript, startpos = self._addCode( + dmscript, code, "", + self.getSeparateThreadStartCode, startpos + ) + if isinstance(kind, str): kind = kind.lower() @@ -618,11 +615,24 @@ def getExecDMScriptCode(self) -> str: dmscript, code, source, script, startpos ) - code = self.getSeparateThreadEndCode() - dmscript, startpos = self._addCode( - dmscript, code, "", - self.getSeparateThreadEndCode, startpos - ) + code = self.getSeparateThreadEndCode(i) + dmscript, startpos = self._addCode( + dmscript, code, "", + self.getSeparateThreadEndCode, startpos + ) + + # for i in wait_for_signals: + # code = self.getSeparateThreadWaitCode(i) + # dmscript, startpos = self._addCode( + # dmscript, code, "".format(i), + # self.getSeparateThreadWaitCode, startpos + # ) + + # the code for the readvars + code = self.getSyncDMCode() + dmscript, startpos = self._addCode( + dmscript, code, "", self.getSyncDMCode, startpos + ) return "\n".join(dmscript) @@ -676,7 +686,7 @@ def _addCode(self, dmscript: list, code: typing.Union[list, tuple, str], return dmscript, startpos + code_lines + 1 - def getSeparateThreadStartCode(self) -> str: + def getSeparateThreadStartCode(self, index: int) -> str: """Get the dm-script code for executing the complete script in a separate thread. @@ -685,6 +695,11 @@ def getSeparateThreadStartCode(self) -> str: code and end it with the code returned by `DMScriptWrapper.getSeparateThreadEndCode()`. + Parameters + ---------- + index : int + The thread index + Returns ------- str @@ -692,11 +707,13 @@ def getSeparateThreadStartCode(self) -> str: """ return "\n".join(( - "class ExecDMScriptThread{} : Thread{{".format(self._creation_time_id), + "object thread_cancel_signal{}_{} = NewCancelSignal();".format(self._creation_time_id, index), + "object thread_done_signal{}_{} = NewSignal(0);".format(self._creation_time_id, index), + "class ExecDMScriptThread{}_{} : Thread{{".format(self._creation_time_id, index), "void RunThread(object self){" )) - def getSeparateThreadEndCode(self) -> str: + def getSeparateThreadEndCode(self, index: int) -> str: """Get the dm-script code for executing the complete script in a separate thread. @@ -705,6 +722,11 @@ def getSeparateThreadEndCode(self) -> str: code and end it with the code returned by `DMScriptWrapper.getSeparateThreadEndCode()`. + Parameters + ---------- + index : int + The thread index + Returns ------- str @@ -712,10 +734,34 @@ def getSeparateThreadEndCode(self) -> str: """ return "\n".join(( + "// inform that the thread is done now", + "thread_done_signal{}_{}.setSignal();".format(self._creation_time_id, index), "}", # end ExecDMScriptThread::RunThread() "}", # end ExecDMScriptThread class - "alloc(ExecDMScriptThread{}).StartThread();".format( - self._creation_time_id + "alloc(ExecDMScriptThread{}_{}).StartThread();".format( + self._creation_time_id, index + ) + )) + + def getSeparateThreadWaitCode(self, index: int) -> str: + """Get the dm-script code for waiting to complete all separately + started threads. + + Parameters + ---------- + index : int + The thread index + + Returns + ------- + str + The code to append to the dm-script code + """ + + return "\n".join(( + "// wait for the thread {}".format(index), + "thread_done_signal{id}_{i}.WaitOnSignal(infinity(), thread_cancel_signal{id}_{i});".format( + id=self._creation_time_id, i=index ) )) diff --git a/setup.py b/setup.py index c71b723..428b9ad 100644 --- a/setup.py +++ b/setup.py @@ -2,10 +2,12 @@ with open("README.md", "r") as fh: long_description = fh.read() +with open("VERSION", "r") as fh: + version = fh.read() setuptools.setup( name="execdmscript", - version="1.0.2", + version=version, author="miile7", author_email="miile7@gmx.de", description=("A python module for executing DM-Script from python in the " +