diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 000000000..b857d9904
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,9 @@
+[run]
+
+source=
+ source/rafcon
+
+omit =
+ *__init__*
+ */usr/local/lib*
+ */test*
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 4cdf577d0..8b9e8ea54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,11 +50,13 @@ pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
+htmlcov_core/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
+coverage_core.xml
# Translations
*.mo
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 000000000..835b26711
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,108 @@
+include:
+ - project: 'dev/sys/sopl/ar-dev'
+ # release branch
+ ref: 'release/3.x'
+ file:
+ - '/ci/templates/default.yml'
+
+variables:
+ AR_CI_CLANG_FORMAT_DISABLE: "true"
+ AR_CI_CLANG_TIDY_DISABLE: "true"
+ AR_CI_CPPCHECK_DISABLE: "true"
+ AR_CI_FLAKE8_DISABLE: "true"
+ AR_CI_YAPF_DISABLE: "true"
+ AR_CI_DOCS_DISABLE: "false"
+ AR_CI_DEPLOY_CONAN_DISABLE: "true"
+
+# From https://docs.gitlab.com/ee/ci/merge_request_pipelines/#excluding-certain-jobs
+.only-default: &only-default
+ only:
+ # only: [branches, tags] is the default
+ # extend it by merge_reguests
+ # and limit branches to master and release/*
+ # See also: https://docs.gitlab.com/ee/ci/yaml/#onlyexcept-basic
+ - master
+ - develop
+ - /^release/.*$
+ - /^release.*$
+ - tags
+ - merge_requests
+
+docs_test:
+ extends: .docs_test
+ variables:
+ BUILD_DIR: build
+ CONAN_OPTIONS: ""
+ MAKE_CMD_DOCS: "make docs"
+
+pages:
+ extends: .docs_pages
+ variables:
+ BUILD_DIR: build
+ CONAN_OPTIONS: ""
+ MAKE_CMD_DOCS: "make docs"
+ except:
+ variables:
+ - $AR_CI_DOCS_DISABLE == "true"
+
+test py2-tests:
+ <<: *only-default
+ stage: Test
+ tags:
+ - Agile_GUI_Docker
+ script:
+ - pip2 install --user --force "pytest>=3.5,<5" lazy-object-proxy==1.5.0 PyYAML==5.1 yaml-configuration==0.2.5
+ - pip2 install pytest-runner==5.2 # this is py2 compatible, ver 5.3 is not
+ - xvfb-run -as "-screen 0 1920x1200x24" python2 setup.py pytest --addopts '-vx -m "(core or gui) and not unstable and not user_input"'
+
+test py3-tests:
+ <<: *only-default
+ stage: Test
+ tags:
+ - Agile_GUI_Docker
+ script:
+ - pip3 install tox
+ - xvfb-run -as "-screen 0 1920x1200x24" tox -r -c tox_external.ini -e py36
+ coverage: /^TOTAL.+?(\d+\%)$/
+ artifacts:
+ expire_in: 7 days
+ reports:
+ cobertura: coverage.xml
+ paths:
+ - coverage.xml
+ - htmlcov/*
+
+test py3-tests-core:
+ <<: *only-default
+ stage: Test
+ tags:
+ - Agile_GUI_Docker
+ script:
+ - pip3 install tox
+ - xvfb-run -as "-screen 0 1920x1200x24" tox -r -c tox_external.ini -e core
+ coverage: /^TOTAL.+?(\d+\%)$/
+ artifacts:
+ expire_in: 7 days
+ reports:
+ cobertura: coverage_core.xml
+ paths:
+ - coverage_core.xml
+ - htmlcov_core/*
+
+
+test py3-memory-test-core:
+ <<: *only-default
+ stage: Test
+ tags:
+ - Agile_GUI_Docker
+ script:
+ - pip3 install tox
+ - xvfb-run -as "-screen 0 1920x1200x24" tox -r -c tox_external.ini -e memory_core
+ artifacts:
+ expire_in: 7 days
+
+
+# running pytest directly does not work
+# as the test_start_script.py test_start_script_valid_config always uses "python" to start "rafcon_core"
+# which fails if tests are run using python3 as the "python" always maps to python2 on Ubuntu 18.04
+# - xvfb-run -as "-screen 0 1920x1200x24" python3 setup.py pytest --addopts '-vx -m "(core or gui) and not unstable and not user_input"'
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
index 69987b2fa..105ce2da2 100644
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Start_GUI.xml b/.idea/runConfigurations/Start_GUI.xml
index d81f909d5..c34150791 100644
--- a/.idea/runConfigurations/Start_GUI.xml
+++ b/.idea/runConfigurations/Start_GUI.xml
@@ -6,7 +6,7 @@
-
+
@@ -16,6 +16,8 @@
-
+
+
+
-
+
\ No newline at end of file
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6c02b65c8..98efcd379 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,22 +5,89 @@ Information about :ref:`RAFCON` changes in each release will be published here.
details can be found in the `GIT commit log `__.
-Patch releases 0.14.\*
-----------------------
+0.15.4
+""""""
+
+- Features:
+ - last version with python2 support which is still supported by Agile Robots
+ - passes testing pipeline for both python2 and python3 inside the CI pipeline of AR
+
+
+0.15.3
+"""""""
+
+- Bug Fixes:
+ - Fix bug in LoggingView, which freezes RAFCON
-Next Release
-""""""""""""
+0.15.2
+"""""""
+
+- Bug Fixes:
+ - Make operations on the logging console thread-safe
+ - Define a new GUI config called 'MAX_LOGGING_BUFFER_LINES' that determines the maximum lines of the logging buffer. If the number of lines exceeds the config value, the old value will be deleted automatically via clipping.
+
+
+0.15.1
+"""""""
+
+- Bug Fixes:
+ - Call 'show_notification' via 'idle_add'
+
+
+0.15.0
+"""""""
- Features:
+ - Libraries can now be renamed and relocated. This includes single libraries, library folders and library root keys
+ - Ctrl+F can be used to search for states
+ - Missing libraries are supported better. In case a library cannot be found, the transitions and data-flows are preserved and added to the dummy-state, which is inserted instead of the library. Furthermore, the dummy-state has the same position and size as the old library state.
+ - New execution-history structure: Define specific consumers for in-memory-execution-history and file-system execution history. Furthermore, another hook was defined such that RAFCON plugins can be used to define further consumers. Watch out: the config values for controlling the execution history changed
+0.14.11
+"""""""
+
+- Features:
+ - Add search bar for lookup through state machine libraries
+ - Add find usage for finding the usages of state machine libraries
+
- Bug Fixes:
+ - Fix handling of library interface change
-- Miscellaneous:
+0.14.10
+"""""""
+
+- Features:
+ - Add new config (``RAISE_ERROR_ON_MISSING_LIBRARY_STATES``) to make Rafcon raise error when loading
+
+
+0.14.9
+""""""
+
+- Features:
+ - add states for execution control
+
+
+0.14.8
+""""""
+
+- Bug Fixes:
+ - Fix py2 support
+0.14.7
+""""""
+
+- Features:
+ - increase test coverage
+ - add gitlab runners support
+ - differentiate between py3 and py2 dependencies in setup.py
+ - differentiate between EXECUTION_LOG_ENABLE and EXECUTION_LOG_TO_FILESYSTEM_ENABLE config options i.e. keep memory footprint of RAFCON constant
+ - add memory leak test
+ - Fix race condition in 'call_gui_callback'
+
0.14.6
""""""
@@ -95,7 +162,7 @@ Maintenance release.
- Improvements:
- - most ``[PyGTK]DeprecatedWarning``s are fixed
+ - most ``[PyGTK]DeprecatedWarning``\s are fixed
- graphical editor: minor performance optimizations
- specify separators for JSON files: Python 3.4 no longer changes the whitespaces in state machine files
- override builtins string in JSON files: state machine files generated by Python 2 and 3 are now fully identical
@@ -107,7 +174,7 @@ Maintenance release.
- better defaults:
- root state is named "root state", further states "[state type] [states counter]"
- - script of ``ExecutionState``s uses more RAFCON features (``preemptive_wait``, return outcome name)
+ - script of ``ExecutionState``\s uses more RAFCON features (``preemptive_wait``, return outcome name)
- name of states uses full width of state
- provide RAFCON wheel file
@@ -195,7 +262,7 @@ Patch releases 0.13.\*
- optimize setup_requires in setup.py (faster installation)
- mark unreliable tests as unstable
- define timeouts for all tests
-
+
- Bug Fixes:
- :issue_ghe:`689` rafcon cannot run without numpy
@@ -211,15 +278,14 @@ Patch releases 0.13.\*
- add ExecutionTicker to see activity of state machine with high hierarchy depth
- Improvements:
-
+
- changing states (adding or removing) during step mode works now
- Bug Fixes:
- :issue_ghe:`678` script validation does not work
- :issue_ghe:`663` cannot rename connected data port of type object
- - :issue_ghe:`684` ``test_simple_execution_model_and_core_destruct_with_gui`` fails when running core & gui tests
- in a row
+ - :issue_ghe:`684` ``test_simple_execution_model_and_core_destruct_with_gui`` fails when running core & gui tests in a row
- fix pause and step mode behavior
- installation of fonts under Python 3
- various test fixed for Python 3
@@ -256,7 +322,7 @@ Patch releases 0.13.\*
- Changes:
- - Release correct style files
+ - Release correct style files
0.13.2
@@ -422,21 +488,21 @@ Patch releases 0.12.\*
"""""""
- Features:
-
+
- maintenance release
0.12.19
"""""""
- Bug Fixes:
-
+
- fix setup.py, sdist now working on pypi
0.12.18
"""""""
- Features:
-
+
- new shortcut open library state separately as state machine by default on 'Shift+Ctrl+Space' (shortcut works for multiple states, too)
- Improvements:
@@ -580,7 +646,7 @@ Patch releases 0.12.\*
- copy/paste for semantic data elements
- new config value SHOW_PATH_NAMES_IN_EXECUTION_HISTORY
- make library path in state editor overview selectable
-
+
- Bug Fixes:
- :issue_ghe:`503` scoped variable looks weird
@@ -893,7 +959,7 @@ Patch releases 0.10.\*
""""""
- Bug Fixes:
-
+
- make execution logs compatible with execution log viewer again
@@ -901,13 +967,13 @@ Patch releases 0.10.\*
""""""
- Improvements:
-
+
- complex actions(copy & paste, resize) are properly handled in gaphas and in the modification history
- :issue_ghe:`342` drag and drop now drops the state at the mouse position
- Bug Fixes:
-
- - show library content for OpenGL works again
+
+ - show library content for OpenGL works again
- add as template works again
- :issue_ghe:`343` Text field does not follow cursor
@@ -918,7 +984,7 @@ Patch releases 0.9.\*
"""""
- Improvements:
-
+
- execution history can be logged and is configurable via the config.yaml
0.9.7
@@ -990,7 +1056,7 @@ Patch releases 0.9.\*
"""""
- Bug Fix
- - fix bad storage format in combination with wrong jsonconversion version
+ - fix bad storage format in combination with wrong jsonconversion version
0.9.0
"""""
@@ -1031,7 +1097,7 @@ Patch releases 0.9.\*
- :issue_ghe:`251`: Handles are added when hovering over a transition handle
- :issue_ghe:`259`: Do not hard code version in about dialog
- :issue_ghe:`260`: Meta data is loaded several times
-
+
Patch releases 0.8.\*
---------------------
@@ -1059,7 +1125,7 @@ Patch releases 0.8.\*
0.8.1
"""""
-
+
- Features:
- renaming of module paths: core instead of state machine; gui instead of mvc
diff --git a/CITATION.cff b/CITATION.cff
index f7ca780aa..29bd81353 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -3,7 +3,7 @@
cff-version: "1.0.3"
message: "If you use this software, please cite it using these metadata."
title: RAFCON
-version: 0.14
+version: 0.15
date-released: 2019-07-29
license: EPL-1.0
doi: "10.5281/zenodo.1493058"
diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000..f327f7ada
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,27 @@
+.PHONY: all docs build clean help
+
+
+all: docs
+
+
+# generate build folders
+build:
+ mkdir -p build/docs
+
+docs: build
+ echo "$(dirname "$1")"
+ pip3 install virtualenv || return -1
+ python3 -m virtualenv venv --system-site-packages || return -1
+ # Don't use the source command since not all shells support it.
+ . venv/bin/activate;\
+ pip3 install -r requirements-py3.txt;\
+ sphinx-build -v -b html doc build/docs/html
+
+clean:
+ rm -rf build
+ rm -rf venv
+
+help:
+ @echo "Available Targets:"
+ @echo "... all"
+ @echo "... docs"
\ No newline at end of file
diff --git a/README.rst b/README.rst
index 2228e3a48..1bf0f496b 100644
--- a/README.rst
+++ b/README.rst
@@ -2,12 +2,12 @@
RAFCON
======
-.. figure:: https://raw.githubusercontent.com/DLR-RM/RAFCON/master/documents/assets/Screenshot_Drill_Skill.png
+.. figure:: documents/assets/Screenshot_Drill_Skill_Scaled.png
:figwidth: 100%
:width: 800px
:align: left
:alt: Screenshot showing RAFCON with a big state machine
- :target: documents/assets/Screenshot_Drill_Skill.png?raw=true
+ :target: documents/assets/Screenshot_Drill_Skill_Scaled.png?raw=true
* Documentation: Hosted on `Read the Docs `_
* Homepage: `DLR-RM.github.io/RAFCON/ `_
diff --git a/VERSION b/VERSION
index 226468ee5..7ffdfa1ca 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.14.6
+0.15.4
diff --git a/build_conan.sh b/build_conan.sh
new file mode 100755
index 000000000..2cfb08cea
--- /dev/null
+++ b/build_conan.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+if [ "$#" -ne 1 ]; then
+ USER_CHANNEL=ar/stable
+else
+ USER_CHANNEL=$1
+fi
+
+echo "Using user and channel: $USER_CHANNEL"
+
+conan create . $USER_CHANNEL -o python3=False
+conan create . $USER_CHANNEL -o python3=True
diff --git a/conanfile.py b/conanfile.py
new file mode 100644
index 000000000..0f46d7fe0
--- /dev/null
+++ b/conanfile.py
@@ -0,0 +1,94 @@
+from conans import ConanFile, CMake, tools
+import os
+import subprocess
+import arpm
+
+
+def get_version():
+ try:
+ # see https://docs.conan.io/en/1.4/howtos/capture_version.html
+ content = tools.load("VERSION")
+ return content
+ except FileNotFoundError as e:
+ return None
+
+
+class RafconConan(ConanFile):
+ name = "rafcon"
+ version = get_version()
+ license = "Eclipse Public License 1.0"
+ author = ('Sebastian Brunner ,'
+ 'Rico Belder ,'
+ 'Franz Steinmetz
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/faq.rst b/doc/faq.rst
index b327e34d9..d5a2deaea 100644
--- a/doc/faq.rst
+++ b/doc/faq.rst
@@ -308,7 +308,7 @@ done if the type of the string matters.
.. _faq_event_based:
Why is RAFCON not event-based?
-"""""""""""""""""""""""""""""""""""""""""""""""""""
+""""""""""""""""""""""""""""""
**tl; dr**: RAFCON was created to enable the development of goal-driven behavior, in contrast to reactive behavior
(for which many frameworks rely on events).
@@ -354,6 +354,7 @@ Now try to answer the following questions, that would be raised during runtime:
3. What is the context (hierarchy level, possible predecessors, possible successors) of each of those state?
4. Since when do I listen to event_x? Until which state will I listen to event_x?
5. I receive an event that I cannot process know. I defer it to a later point in time (*event deferral*).
+
* How long are events valid (*event caching*)?
* Another event with the same type arrives meanwhile. Should I keep the old event (*event expiration*)?
* Another event, which is more important arrives. Should I react to the new event and preempt the current event handling (*event priorization*)?
@@ -482,6 +483,7 @@ that.
Open a new terminal, run the following command and restart RAFCON.
.. code:: bash
+
$ fdir="$HOME/.local/share/fonts" && mkdir -p $fdir && find "$(dirname $(which rafcon))/../share/fonts" -type f -name "*.otf" -exec cp -t $fdir {} + && unset fdir
This will copy the RAFCON font files from the install location to your local user, so the RAFCON GUI can load them.
diff --git a/doc/tutorials.rst b/doc/tutorials.rst
index 5ab37a241..77f421ce9 100644
--- a/doc/tutorials.rst
+++ b/doc/tutorials.rst
@@ -187,7 +187,7 @@ The following code blocks include code lines to generate the correct environment
in an e.g. Ubuntu setup, where the environment is statically specified
in the ~/.bashrc these environment generating commands can be omitted:
-.. code:: python
+.. code:: bash
rmpm_do env ros.indigo.desktop > /tmp/desktop.env
source /tmp/desktop.env
@@ -452,7 +452,7 @@ and the client:
If everything went fine, we should see below output in the debug console
of the client:
-.. code:: python
+.. code::
11:23:40 INFO - monitoring.client: Connect to server ('127.0.0.1', 9999)!
11:23:40 INFO - monitoring.client: self.connector
diff --git a/documents/assets/Screenshot_Drill_Skill_Scaled.png b/documents/assets/Screenshot_Drill_Skill_Scaled.png
new file mode 100644
index 000000000..45c5889a4
Binary files /dev/null and b/documents/assets/Screenshot_Drill_Skill_Scaled.png differ
diff --git a/pytest-cov_results.xml b/pytest-cov_results.xml
new file mode 100644
index 000000000..e69de29bb
diff --git a/pytest.ini b/pytest.ini
index 09d878a64..8cfa5bc42 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -3,7 +3,7 @@
addopts = -x -p no:pytest_capturelog -p no:pytest_catchlog --tb=native
testpaths = tests
norecursedirs = .git all source share doc
-timeout = 20
+timeout = 60
timeout_method = signal
markers =
unstable: mark a test as not running reliable all the time
diff --git a/requirements-py3.txt b/requirements-py3.txt
index c3da4813a..47e745962 100644
--- a/requirements-py3.txt
+++ b/requirements-py3.txt
@@ -2,19 +2,22 @@ astroid==1.6.5
backports.functools-lru-cache==1.5
configparser==3.5.0
decorator==4.3.0
-future==0.17.1
-gaphas==1.0.0rc1
+future>=0.16,<0.18.0
+gaphas~=1.0.0
isort==4.3.4
-jsonconversion==0.2.9
-lazy-object-proxy==1.3.1
+jsonconversion~=0.2.12
mccabe==0.6.1
psutil==5.7.0
pycairo
PyGObject
-pylint==1.9.3
-python-gtkmvc3-dlr==1.0.1
+pylint==1.6
+python-gtkmvc3-dlr~=1.0.0
simplegeneric==0.8.1
singledispatch==3.4.0.3
six==1.12.0
wrapt==1.10.11
-yaml-configuration>=0.1.7
+yaml_configuration~=0.1
+pytest-runner
+libsass >= 0.15.0
+docutils==0.17.1
+
diff --git a/requirements.txt b/requirements.txt
index 83bef8536..cece90c98 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,20 +3,21 @@ backports.functools-lru-cache==1.5
configparser==3.5.0
decorator==4.3.0
enum34==1.1.6
-future==0.17.1
+future>=0.16,<0.18.0
futures==3.2.0
-gaphas==1.0.0rc1
-isort==4.3.4
+gaphas~=1.0.0
+isort==4.2.5
jsonconversion==0.2.9
-lazy-object-proxy==1.3.1
mccabe==0.6.1
psutil==5.7.0
pycairo
PyGObject
-pylint==1.9.3
-python-gtkmvc3-dlr==1.0.1
+pylint==1.6
+python-gtkmvc3-dlr~=1.0.0
simplegeneric==0.8.1
singledispatch==3.4.0.3
-six==1.12.0
+six~=1.11.0
wrapt==1.10.11
yaml-configuration>=0.1.7
+libsass >= 0.15.0
+docutils==0.17.1
diff --git a/setup.py b/setup.py
index ab1bb3b6a..d87a9c399 100755
--- a/setup.py
+++ b/setup.py
@@ -12,10 +12,10 @@
# Rico Belder
# Sebastian Brunner
-from __future__ import print_function
+# from __future__ import print_function
from setuptools import setup, find_packages, Command
import distutils.log
-
+import sys
import os
distutils.log.set_verbosity(distutils.log.INFO)
@@ -62,11 +62,22 @@ def get_data_files():
with open(readme_file_path, "r") as f:
long_description = f.read()
-global_requirements = ['pylint>=1.6,<2', 'psutil', 'jsonconversion~=0.2.12', 'yaml_configuration~=0.1',
- 'python-gtkmvc3-dlr~=1.0.0', 'gaphas~=1.0.0', 'future>=0.16,<0.18.0']
-test_requirements = ['pytest>=3.5,<5', 'pytest-timeout', 'pytest-mock', 'pytest-faulthandler~=1.6.0',
- 'graphviz', 'pyuserinput']
+if sys.version_info[0] < 3: # python2
+ global_requirements = ['pylint==1.6', 'psutil', 'jsonconversion~=0.2.12', 'yaml_configuration~=0.1',
+ 'python-gtkmvc3-dlr~=1.0.0', 'gaphas~=1.0.0', 'future>=0.16,<0.18.0']
+ test_requirements = ['pytest>=3.5,<5', 'pytest-timeout<2', 'pytest-mock>=1.9.0,<3', 'pytest-faulthandler~=1.6.0',
+ 'importlib-metadata~=2.0.0', 'zipp~=1.0.0', 'pyparsing~=2.4.0', 'mock~=3.0.0',
+ 'isort==4.2.5',
+ 'graphviz==0.16', 'pyuserinput', 'pandas', 'numpy']
+ setup_requirements = ['pytest-runner==4.5.1', 'libsass >= 0.15.0', 'six~=1.11.0', 'pathlib']
+else: # python3
+ global_requirements = ['pylint==1.6', 'psutil', 'jsonconversion~=0.2.12', 'yaml_configuration~=0.1',
+ 'python-gtkmvc3-dlr~=1.0.0', 'gaphas~=1.0.0', 'future>=0.16,<0.18.0']
+ test_requirements = ['pytest>=3.5,<5', 'pytest-timeout<2', 'pytest-mock>=1.9.0,<3', 'pytest-faulthandler~=1.6.0',
+ 'graphviz==0.16', 'pyuserinput', 'pandas~=1.1.5', 'numpy~=1.19.5']
+ setup_requirements = ['pytest-runner', 'libsass >= 0.15.0']
+
test_requirements += global_requirements
@@ -115,7 +126,7 @@ def run(self):
data_files=get_data_files(),
- setup_requires=['pytest-runner', 'libsass >= 0.15.0'],
+ setup_requires=setup_requirements,
tests_require=test_requirements,
install_requires=global_requirements,
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/core_data.json b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/core_data.json
index e469d1cf5..e0b27e0a4 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/core_data.json
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/core_data.json
@@ -1,26 +1,29 @@
{
"__jsonqualname__": "rafcon.core.states.barrier_concurrency_state.DeciderState",
"description": null,
+ "income": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Income"
+ },
"input_data_ports": {},
"name": "Decider",
"outcomes": {
"-2": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "preempted",
"outcome_id": -2
},
"-1": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "aborted",
"outcome_id": -1
},
"0": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "success",
"outcome_id": 0
},
"1": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "failure",
"outcome_id": 1
}
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/meta_data.json b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/meta_data.json
index d4e1722fe..d99602036 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/meta_data.json
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/meta_data.json
@@ -1,15 +1,6 @@
{
"gui": {
"editor_gaphas": {
- "income": {
- "rel_pos": {
- "__jsonqualname__": "__builtin__.tuple",
- "items": [
- 0.08,
- 0.4
- ]
- }
- },
"name": {
"rel_pos": {
"__jsonqualname__": "__builtin__.tuple",
@@ -42,6 +33,19 @@
}
}
},
+ "income": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 0.08,
+ 0.4
+ ]
+ }
+ }
+ }
+ },
"outcome-1": {
"gui": {
"editor_gaphas": {
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/script.py b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/script.py
index 0c35e7f3e..4ba223e93 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/script.py
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Decider_unique_decider_state_id/script.py
@@ -1,5 +1,8 @@
from builtins import str
-from exceptions import ZeroDivisionError
+import sys
+
+if sys.version_info[0] < 3:
+ from exceptions import ZeroDivisionError
def execute(self, inputs, outputs, gvm):
self.logger.debug("Executing decider state")
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/First_USMOTU/core_data.json b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/First_USMOTU/core_data.json
index a22198c3e..1b4be0cad 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/First_USMOTU/core_data.json
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/First_USMOTU/core_data.json
@@ -1,21 +1,24 @@
{
"__jsonqualname__": "rafcon.core.states.execution_state.ExecutionState",
"description": null,
+ "income": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Income"
+ },
"input_data_ports": {},
"name": "First",
"outcomes": {
"-2": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "preempted",
"outcome_id": -2
},
"-1": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "aborted",
"outcome_id": -1
},
"0": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "success",
"outcome_id": 0
}
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/First_USMOTU/meta_data.json b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/First_USMOTU/meta_data.json
index 12135cd5e..85f49894b 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/First_USMOTU/meta_data.json
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/First_USMOTU/meta_data.json
@@ -1,15 +1,6 @@
{
"gui": {
"editor_gaphas": {
- "income": {
- "rel_pos": {
- "__jsonqualname__": "__builtin__.tuple",
- "items": [
- 0.08,
- 0.4
- ]
- }
- },
"name": {
"rel_pos": {
"__jsonqualname__": "__builtin__.tuple",
@@ -42,6 +33,19 @@
}
}
},
+ "income": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 0.08,
+ 0.4
+ ]
+ }
+ }
+ }
+ },
"outcome-1": {
"gui": {
"editor_gaphas": {
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Second_MWCTHU/core_data.json b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Second_MWCTHU/core_data.json
index 72ad4f539..009d4c33f 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Second_MWCTHU/core_data.json
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Second_MWCTHU/core_data.json
@@ -1,21 +1,24 @@
{
"__jsonqualname__": "rafcon.core.states.execution_state.ExecutionState",
"description": null,
+ "income": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Income"
+ },
"input_data_ports": {},
"name": "Second",
"outcomes": {
"-2": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "preempted",
"outcome_id": -2
},
"-1": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "aborted",
"outcome_id": -1
},
"0": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "success",
"outcome_id": 0
}
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Second_MWCTHU/meta_data.json b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Second_MWCTHU/meta_data.json
index 05c3eec3b..d41e63820 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Second_MWCTHU/meta_data.json
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/Second_MWCTHU/meta_data.json
@@ -1,15 +1,6 @@
{
"gui": {
"editor_gaphas": {
- "income": {
- "rel_pos": {
- "__jsonqualname__": "__builtin__.tuple",
- "items": [
- 0.08,
- 0.4
- ]
- }
- },
"name": {
"rel_pos": {
"__jsonqualname__": "__builtin__.tuple",
@@ -42,6 +33,19 @@
}
}
},
+ "income": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 0.08,
+ 0.4
+ ]
+ }
+ }
+ }
+ },
"outcome-1": {
"gui": {
"editor_gaphas": {
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/core_data.json b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/core_data.json
index 049d77228..24a05767a 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/core_data.json
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/core_data.json
@@ -2,26 +2,29 @@
"__jsonqualname__": "rafcon.core.states.barrier_concurrency_state.BarrierConcurrencyState",
"data_flows": {},
"description": null,
+ "income": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Income"
+ },
"input_data_ports": {},
"name": "Barrier Concurrency State",
"outcomes": {
"-2": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "preempted",
"outcome_id": -2
},
"-1": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "aborted",
"outcome_id": -1
},
"0": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "success",
"outcome_id": 0
},
"1": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "failure",
"outcome_id": 1
}
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/meta_data.json b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/meta_data.json
index a80b37fd3..1b331f7a8 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/meta_data.json
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/Barrier_Concurrency_State_FUWGAD/meta_data.json
@@ -1,15 +1,6 @@
{
"gui": {
"editor_gaphas": {
- "income": {
- "rel_pos": {
- "__jsonqualname__": "__builtin__.tuple",
- "items": [
- 0.4,
- 2.0
- ]
- }
- },
"name": {
"rel_pos": {
"__jsonqualname__": "__builtin__.tuple",
@@ -42,6 +33,19 @@
}
}
},
+ "income": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 0.4,
+ 2.0
+ ]
+ }
+ }
+ }
+ },
"outcome-1": {
"gui": {
"editor_gaphas": {
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/core_data.json b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/core_data.json
index 7ba827a14..9429a3b40 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/core_data.json
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/core_data.json
@@ -2,26 +2,29 @@
"__jsonqualname__": "rafcon.core.states.hierarchy_state.HierarchyState",
"data_flows": {},
"description": null,
+ "income": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Income"
+ },
"input_data_ports": {},
"name": "Root State",
"outcomes": {
"-2": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "preempted",
"outcome_id": -2
},
"-1": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "aborted",
"outcome_id": -1
},
"0": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "success",
"outcome_id": 0
},
"1": {
- "__jsonqualname__": "rafcon.core.state_elements.outcome.Outcome",
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
"name": "failure",
"outcome_id": 1
}
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/meta_data.json b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/meta_data.json
index 3a9c14a86..a692e82ca 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/meta_data.json
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/Root_State_FMCFIB/meta_data.json
@@ -1,15 +1,6 @@
{
"gui": {
"editor_gaphas": {
- "income": {
- "rel_pos": {
- "__jsonqualname__": "__builtin__.tuple",
- "items": [
- 1.1274158626806232,
- 26.753692650115404
- ]
- }
- },
"name": {
"rel_pos": {
"__jsonqualname__": "__builtin__.tuple",
@@ -42,6 +33,19 @@
}
}
},
+ "income": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 1.1274158626806232,
+ 26.753692650115404
+ ]
+ }
+ }
+ }
+ },
"outcome-1": {
"gui": {
"editor_gaphas": {
diff --git a/share/rafcon/examples/tutorials/barrier_concurrency_state/statemachine.json b/share/rafcon/examples/tutorials/barrier_concurrency_state/statemachine.json
index 78d1d8160..c10a5e081 100644
--- a/share/rafcon/examples/tutorials/barrier_concurrency_state/statemachine.json
+++ b/share/rafcon/examples/tutorials/barrier_concurrency_state/statemachine.json
@@ -1,7 +1,7 @@
{
"creation_time": "2018-07-13 07:09:07",
- "last_update": "2018-08-02 07:23:08",
+ "last_update": "2021-10-20 21:12:23",
"root_state_storage_id": "Root_State_FMCFIB",
"state_machine_version": null,
- "used_rafcon_version": "0.12.17"
+ "used_rafcon_version": "0.14.11a1"
}
\ No newline at end of file
diff --git a/share/rafcon/libraries/generic/execution_control/pause_state_machine/meta_data.json b/share/rafcon/libraries/generic/execution_control/pause_state_machine/meta_data.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/share/rafcon/libraries/generic/execution_control/pause_state_machine/meta_data.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/share/rafcon/libraries/generic/execution_control/pause_state_machine/pause_state_machine_SVHPDB/core_data.json b/share/rafcon/libraries/generic/execution_control/pause_state_machine/pause_state_machine_SVHPDB/core_data.json
new file mode 100644
index 000000000..d73248fc5
--- /dev/null
+++ b/share/rafcon/libraries/generic/execution_control/pause_state_machine/pause_state_machine_SVHPDB/core_data.json
@@ -0,0 +1,28 @@
+{
+ "__jsonqualname__": "rafcon.core.states.execution_state.ExecutionState",
+ "description": null,
+ "income": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Income"
+ },
+ "input_data_ports": {},
+ "name": "pause state machine",
+ "outcomes": {
+ "-2": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
+ "name": "preempted",
+ "outcome_id": -2
+ },
+ "-1": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
+ "name": "aborted",
+ "outcome_id": -1
+ },
+ "0": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
+ "name": "success",
+ "outcome_id": 0
+ }
+ },
+ "output_data_ports": {},
+ "state_id": "SVHPDB"
+}
\ No newline at end of file
diff --git a/share/rafcon/libraries/generic/execution_control/pause_state_machine/pause_state_machine_SVHPDB/meta_data.json b/share/rafcon/libraries/generic/execution_control/pause_state_machine/pause_state_machine_SVHPDB/meta_data.json
new file mode 100644
index 000000000..da8a70101
--- /dev/null
+++ b/share/rafcon/libraries/generic/execution_control/pause_state_machine/pause_state_machine_SVHPDB/meta_data.json
@@ -0,0 +1,88 @@
+{
+ "gui": {
+ "editor_gaphas": {
+ "name": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 0.8766406858462013,
+ 0.8766406858462013
+ ]
+ },
+ "size": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 20.16273577446263,
+ 8.766406858462013
+ ]
+ }
+ },
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 0,
+ 0
+ ]
+ },
+ "size": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 21.91601714615503,
+ 21.91601714615503
+ ]
+ }
+ }
+ },
+ "income": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 0,
+ 2.191601714615503
+ ]
+ }
+ }
+ }
+ },
+ "outcome-1": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 19.724415431539526,
+ 0
+ ]
+ }
+ }
+ }
+ },
+ "outcome-2": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 17.971134059847124,
+ 0
+ ]
+ }
+ }
+ }
+ },
+ "outcome0": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 21.91601714615503,
+ 2.191601714615503
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/share/rafcon/libraries/generic/execution_control/pause_state_machine/pause_state_machine_SVHPDB/script.py b/share/rafcon/libraries/generic/execution_control/pause_state_machine/pause_state_machine_SVHPDB/script.py
new file mode 100644
index 000000000..e404e6c22
--- /dev/null
+++ b/share/rafcon/libraries/generic/execution_control/pause_state_machine/pause_state_machine_SVHPDB/script.py
@@ -0,0 +1,7 @@
+from rafcon.core.singleton import state_machine_execution_engine as smee
+
+
+def execute(self, inputs, outputs, gvm):
+ self.logger.info("Pausing state machine")
+ smee.pause()
+ return "success"
diff --git a/share/rafcon/libraries/generic/execution_control/pause_state_machine/statemachine.json b/share/rafcon/libraries/generic/execution_control/pause_state_machine/statemachine.json
new file mode 100644
index 000000000..33a2cd62a
--- /dev/null
+++ b/share/rafcon/libraries/generic/execution_control/pause_state_machine/statemachine.json
@@ -0,0 +1,7 @@
+{
+ "creation_time": "2021-06-16 11:16:40",
+ "last_update": "2021-06-16 11:18:20",
+ "root_state_storage_id": "pause_state_machine_SVHPDB",
+ "state_machine_version": null,
+ "used_rafcon_version": "0.14.6"
+}
\ No newline at end of file
diff --git a/share/rafcon/libraries/generic/execution_control/stop_state_machine/meta_data.json b/share/rafcon/libraries/generic/execution_control/stop_state_machine/meta_data.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/share/rafcon/libraries/generic/execution_control/stop_state_machine/meta_data.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/share/rafcon/libraries/generic/execution_control/stop_state_machine/statemachine.json b/share/rafcon/libraries/generic/execution_control/stop_state_machine/statemachine.json
new file mode 100644
index 000000000..74b2d35b6
--- /dev/null
+++ b/share/rafcon/libraries/generic/execution_control/stop_state_machine/statemachine.json
@@ -0,0 +1,7 @@
+{
+ "creation_time": "2021-06-16 11:18:07",
+ "last_update": "2021-06-16 11:18:20",
+ "root_state_storage_id": "stop_state_machine_OHPBIV",
+ "state_machine_version": null,
+ "used_rafcon_version": "0.14.6"
+}
\ No newline at end of file
diff --git a/share/rafcon/libraries/generic/execution_control/stop_state_machine/stop_state_machine_OHPBIV/core_data.json b/share/rafcon/libraries/generic/execution_control/stop_state_machine/stop_state_machine_OHPBIV/core_data.json
new file mode 100644
index 000000000..c4827a430
--- /dev/null
+++ b/share/rafcon/libraries/generic/execution_control/stop_state_machine/stop_state_machine_OHPBIV/core_data.json
@@ -0,0 +1,28 @@
+{
+ "__jsonqualname__": "rafcon.core.states.execution_state.ExecutionState",
+ "description": null,
+ "income": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Income"
+ },
+ "input_data_ports": {},
+ "name": "stop state machine",
+ "outcomes": {
+ "-2": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
+ "name": "preempted",
+ "outcome_id": -2
+ },
+ "-1": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
+ "name": "aborted",
+ "outcome_id": -1
+ },
+ "0": {
+ "__jsonqualname__": "rafcon.core.state_elements.logical_port.Outcome",
+ "name": "success",
+ "outcome_id": 0
+ }
+ },
+ "output_data_ports": {},
+ "state_id": "OHPBIV"
+}
\ No newline at end of file
diff --git a/share/rafcon/libraries/generic/execution_control/stop_state_machine/stop_state_machine_OHPBIV/meta_data.json b/share/rafcon/libraries/generic/execution_control/stop_state_machine/stop_state_machine_OHPBIV/meta_data.json
new file mode 100644
index 000000000..da8a70101
--- /dev/null
+++ b/share/rafcon/libraries/generic/execution_control/stop_state_machine/stop_state_machine_OHPBIV/meta_data.json
@@ -0,0 +1,88 @@
+{
+ "gui": {
+ "editor_gaphas": {
+ "name": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 0.8766406858462013,
+ 0.8766406858462013
+ ]
+ },
+ "size": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 20.16273577446263,
+ 8.766406858462013
+ ]
+ }
+ },
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 0,
+ 0
+ ]
+ },
+ "size": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 21.91601714615503,
+ 21.91601714615503
+ ]
+ }
+ }
+ },
+ "income": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 0,
+ 2.191601714615503
+ ]
+ }
+ }
+ }
+ },
+ "outcome-1": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 19.724415431539526,
+ 0
+ ]
+ }
+ }
+ }
+ },
+ "outcome-2": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 17.971134059847124,
+ 0
+ ]
+ }
+ }
+ }
+ },
+ "outcome0": {
+ "gui": {
+ "editor_gaphas": {
+ "rel_pos": {
+ "__jsonqualname__": "__builtin__.tuple",
+ "items": [
+ 21.91601714615503,
+ 2.191601714615503
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/share/rafcon/libraries/generic/execution_control/stop_state_machine/stop_state_machine_OHPBIV/script.py b/share/rafcon/libraries/generic/execution_control/stop_state_machine/stop_state_machine_OHPBIV/script.py
new file mode 100644
index 000000000..d36cfe77e
--- /dev/null
+++ b/share/rafcon/libraries/generic/execution_control/stop_state_machine/stop_state_machine_OHPBIV/script.py
@@ -0,0 +1,7 @@
+from rafcon.core.singleton import state_machine_execution_engine as smee
+
+
+def execute(self, inputs, outputs, gvm):
+ self.logger.info("Stopping state machine")
+ smee.stop()
+ return "success"
diff --git a/share/themes/RAFCON/colors-dark.json b/share/themes/RAFCON/colors-dark.json
index 6916c2361..4493e79ba 100644
--- a/share/themes/RAFCON/colors-dark.json
+++ b/share/themes/RAFCON/colors-dark.json
@@ -30,5 +30,6 @@
"NAME_RESIZE_HANDLE_FILL": "#ddd",
"NAME_RESIZE_HANDLE_BORDER": "#333",
"STATE_MACHINE_ACTIVE": "#39af57",
- "STATE_MACHINE_NOT_ACTIVE": "#fefefe"
+ "STATE_MACHINE_NOT_ACTIVE": "#fefefe",
+ "GUIDE_COLOR": "#00f"
}
diff --git a/share/themes/RAFCON/colors.json b/share/themes/RAFCON/colors.json
index b2603b00e..daa6cd41f 100644
--- a/share/themes/RAFCON/colors.json
+++ b/share/themes/RAFCON/colors.json
@@ -30,5 +30,6 @@
"NAME_RESIZE_HANDLE_FILL": "#ddd",
"NAME_RESIZE_HANDLE_BORDER": "#333",
"STATE_MACHINE_ACTIVE": "#39af57",
- "STATE_MACHINE_NOT_ACTIVE": "#fefefe"
+ "STATE_MACHINE_NOT_ACTIVE": "#fefefe",
+ "GUIDE_COLOR": "#00f"
}
diff --git a/source/rafcon/core/config.yaml b/source/rafcon/core/config.yaml
index d3a165351..21af19c96 100644
--- a/source/rafcon/core/config.yaml
+++ b/source/rafcon/core/config.yaml
@@ -9,13 +9,14 @@ LIBRARY_PATHS: {
}
LIBRARY_RECOVERY_MODE: False
-LOAD_SM_WITH_CHECKS: False
+LOAD_SM_WITH_CHECKS: True
STORAGE_PATH_WITH_STATE_NAME: True
MAX_LENGTH_FOR_STATE_NAME_IN_STORAGE_PATH: None
NO_PROGRAMMATIC_CHANGE_OF_LIBRARY_STATES_PERFORMED: False
-EXECUTION_LOG_ENABLE: False
+IN_MEMORY_EXECUTION_HISTORY_ENABLE: True
+FILE_SYSTEM_EXECUTION_HISTORY_ENABLE: False
EXECUTION_LOG_PATH: "%RAFCON_TEMP_PATH_BASE/execution_logs"
EXECUTION_LOG_SET_READ_AND_WRITABLE_FOR_ALL: False
diff --git a/source/rafcon/core/execution/base_execution_history.py b/source/rafcon/core/execution/base_execution_history.py
new file mode 100644
index 000000000..9cd08d67a
--- /dev/null
+++ b/source/rafcon/core/execution/base_execution_history.py
@@ -0,0 +1,152 @@
+from rafcon.core.execution.execution_history_items import CallItem, ReturnItem, ConcurrencyItem, \
+ StateMachineStartItem, HistoryItem, ScopedDataItem
+from rafcon.core.execution.consumer_manager import ExecutionHistoryConsumerManager
+
+from rafcon.utils import log
+logger = log.get_logger(__name__)
+
+
+class BaseExecutionHistory(object):
+ """A class for the history of a state machine execution
+
+ :ivar initial_prev: optional link to a previous element for the first element pushed into this history of
+ type :class:`rafcon.core.execution.execution_history.HistoryItem`
+ """
+
+ def __init__(self, initial_prev=None, root_state_name="", consumer_manager=None):
+ if not consumer_manager:
+ self.consumer_manager = ExecutionHistoryConsumerManager(root_state_name)
+ else:
+ self.consumer_manager = consumer_manager
+ self.initial_prev = initial_prev
+ # saves the last history item in this variable in order to be able to get the pervious item id
+ self.last_history_item = None
+ self.destroyed = False
+ logger.debug("BaseExecutionHistory has been created")
+
+ def destroy(self):
+ """ Destroy the consumer manager and reset all variables
+ """
+ self.initial_prev = None
+ self.last_history_item = None
+ self.consumer_manager.stop_consumers()
+
+ def shutdown(self):
+ """ Destroy the consumer manager and reset all variables
+ """
+ # we don't want to keep anyting of the last run in memory, thus we can simply destroy the execution history
+ self.destroy()
+
+ # For now implement a "len" functions, as the GUI expects the history to have a len function
+ def __len__(self):
+ return 0
+
+ def get_last_history_item(self):
+ """Returns the history item that was added last
+
+ :return: History item added last
+ :rtype: rafcon.core.execution.execution_history_items.HistoryItem
+ """
+ return self.last_history_item
+
+ # TODO: delete the function from INEH? as it is redundant?
+ def _link_item(self, current_item):
+ """ Link the history item to the previous one
+
+ :param current_item: the history item
+ """
+ last_history_item = self.get_last_history_item()
+ if last_history_item is None:
+ current_item.prev = self.initial_prev
+ if last_history_item is not None:
+ current_item.prev = last_history_item
+ last_history_item.next = current_item
+ self.last_history_item = current_item
+
+ def feed_consumers(self, execution_history_item):
+ """ Add execution history item to the dedicated queue of all consumers
+ and notify their condition variables
+
+ :param execution_history_item: the execution history item
+ """
+ self.consumer_manager.add_history_item_to_queue(execution_history_item)
+
+ def push_call_history_item(self, state, call_type, state_for_scoped_data, input_data=None,
+ link_and_feed_item_to_consumers=True):
+ """ Adds a new call-history-item to the history item list
+
+ A call history items stores information about the point in time where a method (entry, execute,
+ exit) of a certain state was called.
+
+ :param state: the state that was called
+ :param call_type: the call type of the execution step,
+ i.e. if it refers to a container state or an execution state
+ :param state_for_scoped_data: the state of which the scoped data needs to be saved for further usages
+ (e.g. backward stepping)
+ :param link_and_feed_item_to_consumers: if the history item should be feed to all other consumers
+ """
+ from rafcon.core.states.library_state import LibraryState # delayed imported on purpose
+ if isinstance(state_for_scoped_data, LibraryState):
+ state_for_scoped_data = state_for_scoped_data.state_copy
+ history_item = CallItem(state, call_type, state_for_scoped_data, input_data, state.run_id)
+ if link_and_feed_item_to_consumers and self.consumer_manager.consumers_exist:
+ self._link_item(history_item)
+ self.feed_consumers(history_item)
+ return history_item
+
+ def push_return_history_item(self, state, call_type, state_for_scoped_data, output_data=None,
+ link_and_feed_item_to_consumers=True):
+ """ Adds a new return-history-item to the history item list
+
+ A return history items stores information about the point in time where a method (entry, execute,
+ exit) of a certain state returned.
+
+ :param state: the state that returned
+ :param call_type: the call type of the execution step,
+ i.e. if it refers to a container state or an execution state
+ :param state_for_scoped_data: the state of which the scoped data needs to be saved for further usages (e.g.
+ backward stepping)
+ """
+ from rafcon.core.states.library_state import LibraryState # delayed imported on purpose
+ if isinstance(state_for_scoped_data, LibraryState):
+ state_for_scoped_data = state_for_scoped_data.state_copy
+ history_item = ReturnItem(state, call_type, state_for_scoped_data, output_data,
+ state.run_id)
+ if link_and_feed_item_to_consumers and self.consumer_manager.consumers_exist:
+ self._link_item(history_item)
+ self.feed_consumers(history_item)
+ return history_item
+
+ def push_concurrency_history_item(self, state, number_concurrent_threads, link_and_feed_item_to_consumers=True):
+ """ Adds a new concurrency-history-item to the history item list
+
+ A concurrent history item stores information about the point in time where a certain number of states is
+ launched concurrently
+ (e.g. in a barrier concurrency state).
+
+ :param state: the state that launches the state group
+ :param number_concurrent_threads: the number of states that are launched
+ :param link_and_feed_item_to_consumers: if the history item should be feed to all other consumers
+ """
+ history_item = ConcurrencyItem(state, number_concurrent_threads, state.run_id, self.consumer_manager)
+ if link_and_feed_item_to_consumers and self.consumer_manager.consumers_exist:
+ self._link_item(history_item)
+ self.feed_consumers(history_item)
+ return history_item
+
+ def push_state_machine_start_history_item(self, state_machine, run_id, feed_item_to_consumers=True):
+ """ Adds a new state-machine-start-history-item to the history item list
+
+ A state machine starts history item stores information about the point in time where a state machine
+ started to run
+
+ :param state_machine: the state machine that started
+ :param run_id: the run id
+ :param feed_item_to_consumers: if the history item should be feed to all other consumers
+ """
+ history_item = StateMachineStartItem(state_machine, run_id)
+ if feed_item_to_consumers and self.consumer_manager.consumers_exist:
+ self._link_item(history_item)
+ self.feed_consumers(history_item)
+ return history_item
+
diff --git a/source/rafcon/core/execution/consumer_manager.py b/source/rafcon/core/execution/consumer_manager.py
new file mode 100644
index 000000000..08dc381cb
--- /dev/null
+++ b/source/rafcon/core/execution/consumer_manager.py
@@ -0,0 +1,118 @@
+import threading
+
+from queue import Queue
+
+from rafcon.core.config import global_config
+from rafcon.core.execution.consumers.file_system_consumer import FileSystemConsumer
+
+from rafcon.utils import plugins
+from rafcon.utils import log
+logger = log.get_logger(__name__)
+
+
+class ExecutionHistoryConsumerManager(object):
+ """ A class for managing all consumers including the consumer plugins
+ """
+
+ FILE_SYSTEM_CONSUMER_NAME = "file_system_consumer"
+
+ def __init__(self, root_state_name):
+ self.consumers = dict()
+ # Queue with infinite space
+ self.execution_history_item_queue = Queue()
+ self.condition = threading.Condition()
+ self.interrupt = False
+ self._consumers_exist = False
+ self._file_system_consumer_exists = False
+ if global_config.get_config_value("FILE_SYSTEM_EXECUTION_HISTORY_ENABLE", False):
+ self.register_consumer(self.FILE_SYSTEM_CONSUMER_NAME, FileSystemConsumer(root_state_name))
+ self._file_system_consumer_exists = True
+ plugins.run_hook("register_execution_history_consumer", self)
+ # Only have one thread here that will call the notify function of each consumer
+ # The advantage it that the consumer authors don't have to care about threading
+ # and don't have to care about when an item is popped from the queue
+ self.worker_thread = threading.Thread(target=self._feed_consumers)
+ self.worker_thread.start()
+
+ @property
+ def consumers_exist(self):
+ return self._consumers_exist
+
+ @consumers_exist.setter
+ def consumers_exist(self, value):
+ self._consumers_exist = value
+
+ @property
+ def file_system_consumer_exists(self):
+ """ Check if the file system consumer is activated
+ """
+ return self._file_system_consumer_exists
+
+ def get_file_system_consumer_file_name(self):
+ """ Get the filename of the shelve
+ """
+ if self.FILE_SYSTEM_CONSUMER_NAME in self.consumers.keys():
+ return self.consumers[self.FILE_SYSTEM_CONSUMER_NAME].filename
+
+ def stop_consumers(self):
+ """ Stop the working thread and unregister all consumers
+ """
+ self.stop_worker_thread()
+ for consumer in self.consumers.values():
+ self.unregister_consumer(consumer)
+
+ def stop_worker_thread(self):
+ """ Stop the working thread by setting interrupt to true
+ """
+ self.interrupt = True
+ with self.condition:
+ self.condition.notify()
+ self.worker_thread.join()
+
+ def register_consumer(self, consumer_name, consumer):
+ """ Register a specific consumer
+
+ :param consumer_name: the consumer name
+ :param consumer: an instance of the consumer
+ """
+ self.consumers_exist = True
+ self.consumers[consumer_name] = consumer
+ consumer.register()
+
+ def add_history_item_to_queue(self, execution_history_item):
+ """ Add execution history item to the dedicated queue of all consumers
+ and notify their condition variables
+
+ :param execution_history_item: the execution history item
+ """
+ with self.condition:
+ self.execution_history_item_queue.put(execution_history_item)
+ self.condition.notify()
+
+ def _feed_consumers(self):
+ """ Distribute the available execution history items to the consumers
+ """
+ while not self.interrupt:
+ with self.condition:
+ # Python 2.7 does not support wait_for unfortunately, so let's do it manually in a while loop
+ # self.condition.wait_for(lambda: not self.execution_history_item_queue.empty() or self.interrupt)
+ while self.execution_history_item_queue.empty() and not self.interrupt:
+ self.condition.wait()
+ while not self.execution_history_item_queue.empty():
+ next_execution_history_event = self.execution_history_item_queue.get(block=False)
+ self._notifyConsumers(next_execution_history_event)
+
+ def _notifyConsumers(self, execution_history_event):
+ """ Add execution history item to the dedicated queue of all consumers
+ """
+ for client in self.consumers.values():
+ client.enqueue(execution_history_event)
+
+ def unregister_consumer(self, consumer):
+ """ Unegister a specific consumer
+
+ :param consumer: an instance of the consumer
+ """
+ consumer.stop()
+ # Unregister the consumer after stopping the thread to avoid e.g., writing on the closed resources
+ consumer.unregister()
diff --git a/source/rafcon/core/execution/consumers/__init__.py b/source/rafcon/core/execution/consumers/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/source/rafcon/core/execution/consumers/abstract_execution_history_consumer.py b/source/rafcon/core/execution/consumers/abstract_execution_history_consumer.py
new file mode 100644
index 000000000..451b05623
--- /dev/null
+++ b/source/rafcon/core/execution/consumers/abstract_execution_history_consumer.py
@@ -0,0 +1,59 @@
+from queue import Queue
+from threading import Thread, Condition
+
+
+class AbstractExecutionHistoryConsumer(object):
+ """A class that should be the base for every defined consumer
+ """
+ def __init__(self):
+ self._queue = Queue()
+ self._thread = Thread(target=self.worker)
+ self._condition = Condition()
+ self._stop = False
+ self._thread.start()
+
+ def register(self):
+ """ Override the register for the consumer to run the required procedures when a consumer starts
+ """
+ raise NotImplementedError("The register function has to be implemented")
+
+ def consume(self, execution_history_item):
+ """ Override the register for the consumer to run the required procedures when a consumer wants to
+ consume an execution history item
+ """
+ raise NotImplementedError("The consume function has to be implemented")
+
+ def unregister(self):
+ """ Override the register for the consumer to run the required procedures when a consumer stops
+ """
+ raise NotImplementedError("The unregister function has to be implemented")
+
+ def enqueue(self, execution_history_item):
+ """ Add the execution history item to the local consumer queue
+
+ :param execution_history_item: the execution history item
+ """
+ with self._condition:
+ self._queue.put(execution_history_item, block=False)
+ self._condition.notify()
+
+ def worker(self):
+ """ Consume the available execution history item until the thread stops
+ """
+ while not self._stop:
+ with self._condition:
+ # Python 2.7 does not support wait_for unfortunately, so let's do it manually in a while loop
+ # self._condition.wait_for(lambda: not self._queue.empty() or self._stop)
+ while self._queue.empty() and not self._stop:
+ self._condition.wait()
+ while not self._queue.empty():
+ item = self._queue.get(block=False)
+ self.consume(item)
+
+ def stop(self):
+ """ Stop the consumer thread
+ """
+ self._stop = True
+ with self._condition:
+ self._condition.notify()
+ self._thread.join()
diff --git a/source/rafcon/core/execution/consumers/file_system_consumer.py b/source/rafcon/core/execution/consumers/file_system_consumer.py
new file mode 100644
index 000000000..8ea1e1161
--- /dev/null
+++ b/source/rafcon/core/execution/consumers/file_system_consumer.py
@@ -0,0 +1,113 @@
+import os
+import datetime
+import shelve
+import subprocess
+from threading import Lock
+from future.utils import native_str
+
+from rafcon.core.config import global_config
+from rafcon.core.execution.consumers.abstract_execution_history_consumer import AbstractExecutionHistoryConsumer
+from rafcon.utils.constants import RAFCON_TEMP_PATH_BASE
+from rafcon.utils import log
+logger = log.get_logger(__name__)
+
+
+class FileSystemConsumer(AbstractExecutionHistoryConsumer):
+ """ A class that consumes an execution history event and writes it onto the file system.
+ """
+ def __init__(self, root_state_name):
+ super(FileSystemConsumer, self).__init__()
+ self.destroyed = False
+ self.filename = self._get_storage_path_on_file_system(root_state_name)
+ self.store_lock = Lock()
+
+ def register(self):
+ """ Open the shelve file
+ """
+ try:
+ # 'c' for read/write/create
+ # protocol 2 cause of in some cases smaller file size
+ # writeback disabled, cause we don't need caching of entries in memory but continuous writes to the disk
+ self.store = shelve.open(self.filename, flag='c', protocol=2, writeback=False)
+ logger.debug('Openend log file for writing %s' % self.filename)
+ except Exception:
+ logger.exception('Exception:')
+
+ def consume(self, execution_history_item):
+ """ Write to the store variable corresponding to a shelve file
+ """
+ self._store_item(execution_history_item.history_item_id, execution_history_item.to_dict())
+
+ def unregister(self):
+ """ Flush & close the shelve file
+ """
+ set_read_and_writable_for_all = global_config.get_config_value("EXECUTION_LOG_SET_READ_AND_WRITABLE_FOR_ALL",
+ False)
+ self._flush()
+ self._close(set_read_and_writable_for_all)
+
+ @staticmethod
+ def _get_storage_path_on_file_system(root_state_name):
+ """ Get the shelve file of a specific state machine
+
+ :param root_state_name: the root name
+ """
+ base_dir = global_config.get_config_value("EXECUTION_LOG_PATH", "%RAFCON_TEMP_PATH_BASE/execution_logs")
+ if base_dir.startswith('%RAFCON_TEMP_PATH_BASE'):
+ base_dir = base_dir.replace('%RAFCON_TEMP_PATH_BASE', RAFCON_TEMP_PATH_BASE)
+ if not os.path.exists(base_dir):
+ os.makedirs(base_dir)
+ shelve_name = os.path.join(base_dir, '%s_rafcon_execution_log_%s.shelve' %
+ (str(datetime.datetime.now()),
+ root_state_name.replace(' ', '-')))
+ return shelve_name
+
+ def _store_item(self, key, value):
+ """ Flush & close the shelve file
+ """
+ with self.store_lock:
+ try:
+ self.store[native_str(key)] = value
+ except Exception:
+ logger.exception('Exception:')
+
+ def _flush(self):
+ """ Flush the shelve file
+ """
+ with self.store_lock:
+ try:
+ self.store.close()
+ self.store = shelve.open(self.filename, flag='c', protocol=2, writeback=False)
+ logger.debug('Flushed log file %s' % self.filename)
+ except Exception:
+ if self.destroyed:
+ pass # this is fine
+ else:
+ logger.exception('Exception:')
+
+ def _close(self, make_read_and_writable_for_all=False):
+ """ Close the shelve file
+ """
+ with self.store_lock:
+ try:
+ self.store.close()
+ logger.debug('Closed log file %s' % self.filename)
+ if make_read_and_writable_for_all:
+ ret = subprocess.call(['chmod', 'a+rw', self.filename])
+ if ret:
+ logger.debug('Could not make log file readable for all. chmod a+rw failed on %s.' % self.filename)
+ else:
+ logger.debug('Set log file readable for all via chmod a+rw, file %s' % self.filename)
+ except Exception:
+ logger.exception('Exception:')
+
+ def __del__(self):
+ """ Close the shelve file
+ """
+ with self.store_lock:
+ self.destroyed = True
+ try:
+ self.store.close()
+ logger.debug('Closed log file %s' % self.filename)
+ except Exception:
+ logger.exception('Exception:')
diff --git a/source/rafcon/core/execution/execution_engine.py b/source/rafcon/core/execution/execution_engine.py
index d7492b201..b06a39696 100644
--- a/source/rafcon/core/execution/execution_engine.py
+++ b/source/rafcon/core/execution/execution_engine.py
@@ -66,6 +66,9 @@ def __init__(self, state_machine_manager):
# counts how often a state asks for the current execution status
self.state_counter = 0
self.state_counter_lock = Lock()
+ self.new_execution_command_handled = True
+ self.run_selected_done = False
+ self.run_selected_hierarchy_length = None
@Observable.observed
def pause(self):
@@ -228,6 +231,11 @@ def backward_step(self):
"""Take a backward step for all active states in the state machine
"""
logger.debug("Executing backward step ...")
+
+ if not global_config.get_config_value("IN_MEMORY_EXECUTION_HISTORY_ENABLE", True):
+ logger.error("Backward stepping is not allowed if the execution histories are disabled")
+ return
+
self.run_to_states = []
self.set_execution_mode(StateMachineExecutionStatus.BACKWARD)
@@ -300,6 +308,35 @@ def run_to_selected_state(self, path, state_machine_id=None):
self.run_to_states.append(path)
self._run_active_state_machine()
+ def run_selected_state(self, start_state_path=None, state_machine_id=None):
+ """Execute the selected state machine.
+ """
+ logger.debug("Run selected state")
+ if state_machine_id is not None:
+ self.state_machine_manager.active_state_machine_id = state_machine_id
+
+ self.run_to_states = []
+ # set appropriate start states
+ self.start_state_paths = []
+ if start_state_path:
+ path_list = start_state_path.split("/")
+ cur_path = ""
+ for path in path_list:
+ if cur_path == "":
+ cur_path = path
+ else:
+ cur_path = cur_path + "/" + path
+ self.start_state_paths.append(cur_path)
+
+ # set target states when execution should stop
+ self.run_to_states.append(start_state_path)
+
+ if self.finished_or_stopped():
+ self.set_execution_mode(StateMachineExecutionStatus.RUN_SELECTED_STATE)
+ self._run_active_state_machine()
+ else:
+ self.set_execution_mode(StateMachineExecutionStatus.RUN_SELECTED_STATE)
+
def _wait_while_in_pause_or_in_step_mode(self):
""" Waits as long as the execution_mode is in paused or step_mode
"""
@@ -328,20 +365,43 @@ def _wait_if_required(self, container_state, next_child_state_to_execute, woke_u
if next_child_state_to_execute:
next_child_state_path = next_child_state_to_execute.get_path()
if state_path == container_state.get_path():
- # the execution did a whole step_over inside hierarchy state "state" (case a) )
- # or a whole step_out into the hierarchy state "state" (case b) )
- # thus we delete its state path from self.run_to_states
- # and wait for another step (of maybe different kind)
- wait = True
- self.run_to_states.remove(state_path)
+ if self._status.execution_mode is StateMachineExecutionStatus.RUN_SELECTED_STATE:
+ # This is the very special case of having selected a direct hierarchy-child-state
+ # of a concurrency state as the state for the "run-selected-state" operation
+ # this basically means that we want to run the whole concurreny state
+ self._status.execution_mode = StateMachineExecutionStatus.FORWARD_OUT
+ self.run_to_states.remove(state_path)
+ # the new target state, when the execution should pause, is the parent of the concurrency state
+ self.run_to_states.append(container_state.parent.parent.get_path())
+ wait = False
+ else:
+ # the execution did a whole step_over inside hierarchy state "state" (case a) )
+ # or a whole step_out into the hierarchy state "state" (case b) )
+ # thus we delete its state path from self.run_to_states
+ # and wait for another step (of maybe different kind)
+ wait = True
+ self.run_to_states.remove(state_path)
break
elif state_path == next_child_state_path:
# this is the case that execution has reached a specific state explicitly marked via
# run_to_selected_state() (case c) )
- # if this is the case run_to_selected_state() is finished and the execution
- # has to wait for new execution commands
- wait = True
- self.run_to_states.remove(state_path)
+ if self._status.execution_mode is StateMachineExecutionStatus.RUN_SELECTED_STATE:
+ # we now reached the state that we wanted to execute using the "run-selected-state" feature
+ # now we just add one FORWARD_OVER step
+ self._status.execution_mode = StateMachineExecutionStatus.FORWARD_OVER
+ self.run_to_states.remove(state_path)
+ # add the container state to self.run_to_states
+ # this means that the execution will stop once it reaches the container state again
+ # this will be the case after the selected-state finished and execution is passed
+ # to the the parent of the selected state, which equals "container_state"
+ self.run_to_states.append(container_state.get_path())
+ wait = False
+ break
+ else:
+ # if this is the case run_to_selected_state() is finished and the execution
+ # has to wait for new execution commands
+ wait = True
+ self.run_to_states.remove(state_path)
break
# don't wait if its just a normal step
else:
@@ -358,7 +418,7 @@ def _wait_if_required(self, container_state, next_child_state_to_execute, woke_u
# if the status was set to PAUSED or STEP_MODE don't wake up!
self._wait_while_in_pause_or_in_step_mode()
# container_state was notified => thus, a new user command was issued, which has to be handled!
- container_state.execution_history.new_execution_command_handled = False
+ self.new_execution_command_handled = False
def handle_execution_mode(self, container_state, next_child_state_to_execute=None):
"""Checks the current execution status and returns it.
@@ -382,8 +442,8 @@ def handle_execution_mode(self, container_state, next_child_state_to_execute=Non
if (self._status.execution_mode is StateMachineExecutionStatus.PAUSED) \
or (self._status.execution_mode is StateMachineExecutionStatus.STEP_MODE):
self._wait_while_in_pause_or_in_step_mode()
- # new command was triggered => execution command has to handled
- container_state.execution_history.new_execution_command_handled = False
+ # new command was triggered => execution command not handled yet
+ self.new_execution_command_handled = False
woke_up_from_pause_or_step_mode = True
# no elif here: if the execution woke up from e.g. paused mode, it has to check the current execution mode
@@ -410,16 +470,16 @@ def handle_execution_mode(self, container_state, next_child_state_to_execute=Non
elif self._status.execution_mode is StateMachineExecutionStatus.FORWARD_INTO:
pass
elif self._status.execution_mode is StateMachineExecutionStatus.FORWARD_OVER:
- if not container_state.execution_history.new_execution_command_handled:
+ if not self.new_execution_command_handled:
# the state that called this method is a hierarchy state => thus we save this state and wait until
- # thise very state will execute its next state; only then we will wait on the condition variable
+ # this very state will execute its next state; only then we will wait on the condition variable
self.run_to_states.append(container_state.get_path())
else:
pass
elif self._status.execution_mode is StateMachineExecutionStatus.FORWARD_OUT:
from rafcon.core.states.state import State
if isinstance(container_state.parent, State):
- if not container_state.execution_history.new_execution_command_handled:
+ if not self.new_execution_command_handled:
from rafcon.core.states.library_state import LibraryState
if isinstance(container_state.parent, LibraryState):
parent_path = container_state.parent.parent.get_path()
@@ -436,7 +496,7 @@ def handle_execution_mode(self, container_state, next_child_state_to_execute=Non
# "run_to_states" were already updated thus doing nothing
pass
- container_state.execution_history.new_execution_command_handled = True
+ self.new_execution_command_handled = True
# in the case that the stop method wakes up the paused or step mode a StateMachineExecutionStatus.STOPPED
# will be returned
@@ -514,9 +574,6 @@ def recompile_execution_state_scripts(state):
state_machine = self.state_machine_manager.get_active_state_machine()
recompile_execution_state_scripts(state_machine.root_state)
-
-
-
@Observable.observed
def set_execution_mode(self, execution_mode, notify=True):
""" An observed setter for the execution mode of the state machine status. This is necessary for the
@@ -557,4 +614,3 @@ def run_to_states(self, run_to_states):
raise TypeError("run_to_states must be of type list")
with self.execution_engine_lock:
self._run_to_states = run_to_states
-
diff --git a/source/rafcon/core/execution/execution_history.py b/source/rafcon/core/execution/execution_history.py
deleted file mode 100644
index e9ad6c9f0..000000000
--- a/source/rafcon/core/execution/execution_history.py
+++ /dev/null
@@ -1,497 +0,0 @@
-# Copyright (C) 2014-2018 DLR
-#
-# All rights reserved. This program and the accompanying materials are made
-# available under the terms of the Eclipse Public License v1.0 which
-# accompanies this distribution, and is available at
-# http://www.eclipse.org/legal/epl-v10.html
-#
-# Contributors:
-# Franz Steinmetz
-# Rico Belder
-# Sebastian Brunner
-# Sebastian Riedel
-# ried_sa
-
-"""
-.. module:: execution_history
- :synopsis: A module for the history of one thread during state machine execution
-
-"""
-from future.utils import native_str
-from builtins import object
-from builtins import range
-from builtins import str
-import time
-import copy
-from collections import Iterable, Sized
-import json
-from jsonconversion.decoder import JSONObjectDecoder
-from jsonconversion.encoder import JSONObjectEncoder
-
-import shelve
-from threading import Lock
-from enum import Enum
-from gtkmvc3.observable import Observable
-
-from rafcon.core.id_generator import history_item_id_generator
-from rafcon.utils import log
-logger = log.get_logger(__name__)
-import os
-import subprocess
-import pickle
-from weakref import ref
-
-
-class ExecutionHistoryStorage(object):
- def __init__(self, filename):
- self.filename = filename
- self.store_lock = Lock()
- try:
- # 'c' for read/write/create
- # protocol 2 cause of in some cases smaller file size
- # writeback disabled, cause we don't need caching of entries in memory but continuous writes to the disk
- self.store = shelve.open(filename, flag='c', protocol=2, writeback=False)
- logger.debug('Openend log file for writing %s' % self.filename)
- except Exception:
- logger.exception('Exception:')
-
- def store_item(self, key, value):
- with self.store_lock:
- try:
- self.store[native_str(key)] = value
- except Exception:
- logger.exception('Exception:')
-
- def flush(self):
- with self.store_lock:
- try:
- self.store.close()
- self.store = shelve.open(self.filename, flag='c', protocol=2, writeback=False)
- logger.debug('Flushed log file %s' % self.filename)
- except Exception:
- if self.destroyed:
- pass # this is fine
- else:
- logger.exception('Exception:')
-
- def close(self, make_read_and_writable_for_all=False):
- with self.store_lock:
- try:
- self.store.close()
- logger.debug('Closed log file %s' % self.filename)
- if make_read_and_writable_for_all:
- ret = subprocess.call(['chmod', 'a+rw', self.filename])
- if ret:
- logger.debug('Could not make log file readable for all. chmod a+rw failed on %s.' % self.filename)
- else:
- logger.debug('Set log file readable for all via chmod a+rw, file %s' % self.filename)
- except Exception:
- logger.exception('Exception:')
-
- def __del__(self):
- with self.store_lock:
- self.destroyed = True
- try:
- self.store.close()
- logger.debug('Closed log file %s' % self.filename)
- except Exception:
- logger.exception('Exception:')
-
-
-class ExecutionHistory(Observable, Iterable, Sized):
- """A class for the history of a state machine execution
-
- It stores all history elements in a stack wise fashion.
-
- :ivar initial_prev: optional link to a previous element for the first element pushed into this history of
- type :class:`rafcon.core.execution.execution_history.HistoryItem`
- """
-
- def __init__(self, initial_prev=None):
- super(ExecutionHistory, self).__init__()
- self._history_items = []
- self.initial_prev = initial_prev
- self.execution_history_storage = None
- self.new_execution_command_handled = True
-
- def destroy(self):
- # logger.verbose("Destroy execution history!")
- if self.execution_history_storage:
- self.execution_history_storage.close()
- self.execution_history_storage = None
- if len(self._history_items) > 0:
- if self._history_items[0]:
- execution_history_iterator = iter(self)
- for history_item in execution_history_iterator:
- history_item.destroy()
- self.destroyed = True
- self._history_items = None
- self.initial_prev = None
-
- def __iter__(self):
- return iter(self._history_items)
-
- def set_execution_history_storage(self, execution_history_storage):
- self.execution_history_storage = execution_history_storage
-
- def __len__(self):
- return len(self._history_items)
-
- def __getitem__(self, index):
- return self._history_items[index]
-
- def get_last_history_item(self):
- """Returns the history item that was added last
-
- :return: History item added last
- :rtype: HistoryItem
- """
- try:
- return self[-1]
- except IndexError: # this is the case for the very first executed state
- return None
-
- def _push_item(self, last_history_item, current_item):
- if last_history_item is None:
- current_item.prev = self.initial_prev
- if last_history_item is not None:
- last_history_item.next = current_item
- if self.execution_history_storage is not None:
- self.execution_history_storage.store_item(current_item.history_item_id, current_item.to_dict())
- try:
- self._history_items.append(current_item)
- except AttributeError:
- if self.destroyed:
- pass # this is fine
- else:
- raise
- return current_item
-
- @Observable.observed
- def push_call_history_item(self, state, call_type, state_for_scoped_data, input_data=None):
- """Adds a new call-history-item to the history item list
-
- A call history items stores information about the point in time where a method (entry, execute,
- exit) of certain state was called.
-
- :param state: the state that was called
- :param call_type: the call type of the execution step,
- i.e. if it refers to a container state or an execution state
- :param state_for_scoped_data: the state of which the scoped data needs to be saved for further usages
- (e.g. backward stepping)
- """
- last_history_item = self.get_last_history_item()
- from rafcon.core.states.library_state import LibraryState # delayed imported on purpose
- if isinstance(state_for_scoped_data, LibraryState):
- state_for_scoped_data = state_for_scoped_data.state_copy
- return_item = CallItem(state, last_history_item, call_type, state_for_scoped_data, input_data,
- state.run_id)
- return self._push_item(last_history_item, return_item)
-
- @Observable.observed
- def push_return_history_item(self, state, call_type, state_for_scoped_data, output_data=None):
- """Adds a new return-history-item to the history item list
-
- A return history items stores information about the point in time where a method (entry, execute,
- exit) of certain state returned.
-
- :param state: the state that returned
- :param call_type: the call type of the execution step,
- i.e. if it refers to a container state or an execution state
- :param state_for_scoped_data: the state of which the scoped data needs to be saved for further usages (e.g.
- backward stepping)
- """
- last_history_item = self.get_last_history_item()
- from rafcon.core.states.library_state import LibraryState # delayed imported on purpose
- if isinstance(state_for_scoped_data, LibraryState):
- state_for_scoped_data = state_for_scoped_data.state_copy
- return_item = ReturnItem(state, last_history_item, call_type, state_for_scoped_data, output_data,
- state.run_id)
- return self._push_item(last_history_item, return_item)
-
- @Observable.observed
- def push_concurrency_history_item(self, state, number_concurrent_threads):
- """Adds a new concurrency-history-item to the history item list
-
- A concurrent history item stores information about the point in time where a certain number of states is
- launched concurrently
- (e.g. in a barrier concurrency state).
-
- :param state: the state that launches the state group
- :param number_concurrent_threads: the number of states that are launched
- """
- last_history_item = self.get_last_history_item()
- return_item = ConcurrencyItem(state, self.get_last_history_item(),
- number_concurrent_threads, state.run_id,
- self.execution_history_storage)
- return self._push_item(last_history_item, return_item)
-
- @Observable.observed
- def push_state_machine_start_history_item(self, state_machine, run_id):
- return_item = StateMachineStartItem(state_machine, run_id)
- if self.execution_history_storage is not None:
- self.execution_history_storage.store_item(return_item.history_item_id, return_item.to_dict())
- self._history_items.append(return_item)
- return return_item
-
- @Observable.observed
- def pop_last_item(self):
- """Delete and returns the last item of the history item list.
-
- :return: History item added last
- :rtype: HistoryItem
- """
- try:
- return self._history_items.pop()
- except IndexError:
- logger.error("No item left in the history item list in the execution history.")
- return None
-
-
-class HistoryItem(object):
- """Class representing an entry within the history
-
- An abstract class that serves as a data structure to hold all important information of a certain point in time
- during the execution of a state machine. A history item is an element in a doubly linked history item list.
-
- :ivar state_reference: a reference to the state performing a certain action that is going to be saved
- :ivar path: the state path
- :ivar timestamp: the time of the call/return
- :ivar prev: the previous history item
- :ivar next: the next history item
- """
-
- def __init__(self, state, prev, run_id):
- self._state_reference = state
- self.path = copy.deepcopy(state.get_path())
- self.timestamp = time.time()
- self.run_id = run_id
- self.prev = prev
- self.next = None
- self.history_item_id = history_item_id_generator()
- self.state_type = str(type(state).__name__)
-
- def destroy(self):
- self._state_reference = None
- self.path = None
- self.timestamp = None
- self.run_id = None
- self.prev = None
- self.next = None
- self.history_item_id = None
- self.state_type = None
-
- @property
- def state_reference(self):
- """Property for the state_reference field
- """
- return self._state_reference
-
- def __str__(self):
- return "HistoryItem with reference state name %s (time: %s)" % (self.state_reference.name, self.timestamp)
-
- def to_dict(self):
- record = dict()
-
- # here always the correct path is desired
- record['path'] = self.state_reference.get_path()
- record['path_by_name'] = self.state_reference.get_path(by_name=True)
- record['state_type'] = str(type(self.state_reference).__name__)
-
- from rafcon.core.states.library_state import LibraryState # delayed imported on purpose
- if isinstance(self.state_reference, LibraryState):
- # in case of a Library State, all the data of the library itself should be used
- record['is_library'] = True
- # TODO: rename key to library_root_state_name? However, this will break many analysis scripts => next minor
- record['library_state_name'] = self.state_reference.state_copy.name
- record['library_name'] = self.state_reference.library_name
- record['library_path'] = self.state_reference.library_path
- target_state = self.state_reference.state_copy
- else:
- record['is_library'] = False
- record['library_state_name'] = None
- record['library_name'] = None
- record['library_path'] = None
- target_state = self.state_reference
-
- # there are 3 names of interest:
- # self.state_reference.library_name (<- name of the library on the filesystem)
- # self.state_reference.name (<- name of the user when using a library and changing the name)
- # self.state_reference.state_copy.name (<- the name of the library root state)
- record['state_name'] = self.state_reference.name
- record['timestamp'] = self.timestamp
- record['run_id'] = self.run_id # library state and state copy have the same run_id
- record['history_item_id'] = self.history_item_id
-
- # semantic data
- semantic_data_dict = {}
- for k, v in target_state.semantic_data.items():
- try:
- semantic_data_dict[k] = pickle.dumps(v)
- except Exception as e:
- semantic_data_dict['!' + k] = (str(e), str(v))
- record['semantic_data'] = semantic_data_dict
-
- record['description'] = target_state.description
-
- if self.prev is not None:
- record['prev_history_item_id'] = self.prev.history_item_id
- else:
- record['prev_history_item_id'] = None
- # store the specialized class name as item_type,
- # e.g. CallItem, ReturnItem, StatemachineStartItem when saved
- record['item_type'] = self.__class__.__name__
- return record
-
-
-class StateMachineStartItem(HistoryItem):
- def __init__(self, state_machine, run_id):
- HistoryItem.__init__(self, state_machine.root_state, None, run_id)
- from rafcon.core.state_machine import StateMachine
- self.sm_dict = StateMachine.state_machine_to_dict(state_machine)
- self.prev = None
- self.os_environment = dict(os.environ)
-
- def __str__(self):
- return "StateMachineStartItem with name %s (time: %s)" % (self.sm_dict['root_state_storage_id'], self.timestamp)
-
- def to_dict(self):
- record = HistoryItem.to_dict(self)
- record.update(self.sm_dict)
- record['call_type'] = 'EXECUTE'
- record['state_name'] = 'StateMachineStartItem'
- record['state_type'] = 'StateMachine'
- record['path'] = ''
- record['path_by_name'] = ''
- record['os_environment'] = self.os_environment
- if self.prev is not None:
- record['prev_history_item_id'] = self.prev.history_item_id
- else:
- record['prev_history_item_id'] = None
- return record
-
-
-class ScopedDataItem(HistoryItem):
- """A abstract class to represent history items which contains the scoped data of a state
-
- :ivar call_type: the call type of the execution step, i.e. if it refers to a container state or an execution state
- :ivar state_for_scoped_data: the state of which the scoped data will be stored as the context data that is necessary
- to re-execute the state
- """
-
- def __init__(self, state, prev, call_type, state_for_scoped_data, child_state_input_output_data, run_id):
- HistoryItem.__init__(self, state, prev, run_id)
- if call_type in CallType:
- self.call_type_str = call_type.name
- else:
- raise Exception('unkown calltype, neither CONTAINER nor EXECUTE')
- self.call_type = call_type
- self.scoped_data = {} if state_for_scoped_data is None else copy.deepcopy(state_for_scoped_data._scoped_data)
- self.child_state_input_output_data = copy.deepcopy(child_state_input_output_data)
-
- def to_dict(self):
- record = HistoryItem.to_dict(self)
- scoped_data_dict = {}
- for k, v in self.scoped_data.items():
- try:
- scoped_data_dict[v.name] = pickle.dumps(v.value)
- except Exception as e:
- scoped_data_dict['!' + v.name] = (str(e), str(v.value))
- # logger.debug('TypeError: Could not serialize one of the scoped data port types.')
- # record['scoped_data'] = json.dumps({'error_type': 'TypeError',
- # 'error_message': e}, cls=JSONObjectEncoder)
- record['scoped_data'] = scoped_data_dict
-
- child_state_input_output_dict = {}
- for k, v in self.child_state_input_output_data.items():
- try:
- child_state_input_output_dict[k] = pickle.dumps(v)
- except Exception as e:
- child_state_input_output_dict['!' + k] = (str(e), str(v))
- record['input_output_data'] = child_state_input_output_dict
-
- # from rafcon.core.states.container_state import ContainerState
- # if isinstance(self.state_reference, ContainerState):
- # try:
- # record['scoped_variables'] = json.dumps(self.state_reference.scoped_variables, cls=JSONObjectEncoder)
- # except TypeError as e:
- # # logger.exception('TypeError: Could not serialize one of the scoped variables types.')
- # # record['scoped_variables'] = json.dumps({'error_type': 'TypeError',
- # # 'error_message': e}, cls=JSONObjectEncoder)
- # record['scoped_variables'] = json.dumps(
- # {"key_all": {"name": "all_scoped_variables_as_string",
- # "value": str(self.state_reference.scoped_variables)}}, cls=JSONObjectEncoder
- # )
- # else:
- # record['scoped_variables'] = json.dumps({})
-
- record['call_type'] = self.call_type_str
- return record
-
- def __str__(self):
- return "SingleItem %s" % (HistoryItem.__str__(self))
-
-
-class CallItem(ScopedDataItem):
- """A history item to represent a state call
- """
- def __init__(self, state, prev, call_type, state_for_scoped_data, input_data, run_id):
- ScopedDataItem.__init__(self, state, prev, call_type, state_for_scoped_data, input_data, run_id)
- self.outcome = None
-
- def __str__(self):
- return "CallItem %s" % (ScopedDataItem.__str__(self))
-
- def to_dict(self):
- record = ScopedDataItem.to_dict(self)
- return record
-
-
-class ReturnItem(ScopedDataItem):
- """A history item to represent the return of a root state call
- """
- def __init__(self, state, prev, call_type, state_for_scoped_data, output_data, run_id):
- ScopedDataItem.__init__(self, state, prev, call_type, state_for_scoped_data, output_data, run_id)
- self.outcome = copy.deepcopy(state.final_outcome)
-
- def __str__(self):
- return "ReturnItem %s" % (ScopedDataItem.__str__(self))
-
- def to_dict(self):
- record = ScopedDataItem.to_dict(self)
- if self.outcome is not None:
- record['outcome_name'] = self.outcome.to_dict()['name']
- record['outcome_id'] = self.outcome.to_dict()['outcome_id']
- else:
- record['outcome_name'] = 'None'
- record['outcome_id'] = -1
- return record
-
-
-class ConcurrencyItem(HistoryItem):
- """A class to hold all the data for an invocation of several concurrent threads.
- """
- def __init__(self, container_state, prev, number_concurrent_threads, run_id, execution_history_storage):
- HistoryItem.__init__(self, container_state, prev, run_id)
- self.execution_histories = []
-
- for i in range(number_concurrent_threads):
- execution_history = ExecutionHistory(initial_prev=self)
- execution_history.set_execution_history_storage(execution_history_storage)
- self.execution_histories.append(execution_history)
-
- def __str__(self):
- return "ConcurrencyItem %s" % (HistoryItem.__str__(self))
-
- def to_dict(self):
- record = HistoryItem.to_dict(self)
- record['call_type'] = 'CONTAINER'
- return record
-
- def destroy(self):
- for execution_history in self.execution_histories:
- execution_history.destroy()
- super(ConcurrencyItem, self).destroy()
-
-
-CallType = Enum('METHOD_NAME', 'EXECUTE CONTAINER')
diff --git a/source/rafcon/core/execution/execution_history_factory.py b/source/rafcon/core/execution/execution_history_factory.py
new file mode 100644
index 000000000..3da72075c
--- /dev/null
+++ b/source/rafcon/core/execution/execution_history_factory.py
@@ -0,0 +1,35 @@
+
+from rafcon.core.config import global_config
+
+from rafcon.core.execution.in_memory_execution_history import InMemoryExecutionHistory
+from rafcon.core.execution.base_execution_history import BaseExecutionHistory
+
+from rafcon.utils import log
+logger = log.get_logger(__name__)
+
+
+class ExecutionHistoryFactory(object):
+ """ A factory class for creating an instance of BaseExecutionHistory or InMemoryExecutionHistory
+ """
+
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def get_execution_history(initial_prev=None, root_state_name="", consumer_manager=None):
+ """ Create an instance of a InMemoryExecutionHistory or BaseExecutionHistory
+
+ :param initial_prev: the initial previous history item
+ :param root_state_name: the root state name
+ :param consumer_manager: the consumer manager
+
+ :return: an instance of BaseExecutionHistory or InMemoryExecutionHistory
+ """
+ if global_config.get_config_value("IN_MEMORY_EXECUTION_HISTORY_ENABLE", True):
+ return InMemoryExecutionHistory(initial_prev=initial_prev,
+ root_state_name=root_state_name,
+ consumer_manager=consumer_manager)
+ else:
+ return BaseExecutionHistory(initial_prev=initial_prev,
+ root_state_name=root_state_name,
+ consumer_manager=consumer_manager)
diff --git a/source/rafcon/core/execution/execution_history_items.py b/source/rafcon/core/execution/execution_history_items.py
new file mode 100644
index 000000000..2b6093267
--- /dev/null
+++ b/source/rafcon/core/execution/execution_history_items.py
@@ -0,0 +1,266 @@
+import copy
+import json
+import os
+import pickle
+import time
+from enum import Enum
+
+from rafcon.core.id_generator import history_item_id_generator
+from rafcon.utils import log
+logger = log.get_logger(__name__)
+
+
+class HistoryItem(object):
+ """Class representing an entry within the history
+
+ An abstract class that serves as a data structure to hold all important information of a certain point in time
+ during the execution of a state machine. A history item is an element in a doubly linked history item list.
+
+ :ivar state_reference: a reference to the state performing a certain action that is going to be saved
+ :ivar path: the state path
+ :ivar timestamp: the time of the call/return
+ :ivar prev: the previous history item
+ :ivar next: the next history item
+ """
+
+ def __init__(self, state, run_id):
+ self._state_reference = state
+ self.path = copy.deepcopy(state.get_path())
+ self.timestamp = time.time()
+ self.run_id = run_id
+ self._prev = None
+ self._next = None
+ self.history_item_id = history_item_id_generator()
+ self.state_type = str(type(state).__name__)
+
+ def destroy(self):
+ self._state_reference = None
+ self.path = None
+ self.timestamp = None
+ self.run_id = None
+ self._prev = None
+ self._next = None
+ self.history_item_id = None
+ self.state_type = None
+
+ @property
+ def prev(self):
+ """Property for the prev field
+ """
+ return self._prev
+
+ @prev.setter
+ def prev(self, prev):
+ if not isinstance(prev, HistoryItem) and (prev is not None):
+ raise TypeError("prev must be of type HistoryItem")
+ self._prev = prev
+
+ @property
+ def next(self):
+ """Property for the next field
+ """
+ return self._next
+
+ @next.setter
+ def next(self, next):
+ if not isinstance(next, HistoryItem) and (next is not None):
+ raise TypeError("next must be of type HistoryItem")
+ self._next = next
+
+ @property
+ def state_reference(self):
+ """Property for the state_reference field
+ """
+ return self._state_reference
+
+ def __str__(self):
+ return "HistoryItem with reference state name %s (time: %s)" % (self.state_reference.name, self.timestamp)
+
+ def to_dict(self, pickled=True):
+ record = dict()
+
+ # here always the correct path is desired
+ record['path'] = self.state_reference.get_path()
+ record['path_by_name'] = self.state_reference.get_path(by_name=True)
+ record['state_type'] = str(type(self.state_reference).__name__)
+
+ from rafcon.core.states.library_state import LibraryState # delayed imported on purpose
+ if isinstance(self.state_reference, LibraryState):
+ # in case of a Library State, all the data of the library itself should be used
+ record['is_library'] = True
+ # TODO: rename key to library_root_state_name? However, this will break many analysis scripts => next minor
+ record['library_state_name'] = self.state_reference.state_copy.name
+ record['library_name'] = self.state_reference.library_name
+ record['library_path'] = self.state_reference.library_path
+ target_state = self.state_reference.state_copy
+ else:
+ record['is_library'] = False
+ record['library_state_name'] = None
+ record['library_name'] = None
+ record['library_path'] = None
+ target_state = self.state_reference
+
+ # there are 3 names of interest:
+ # self.state_reference.library_name (<- name of the library on the filesystem)
+ # self.state_reference.name (<- name of the user when using a library and changing the name)
+ # self.state_reference.state_copy.name (<- the name of the library root state)
+ record['state_name'] = self.state_reference.name
+ record['timestamp'] = self.timestamp
+ record['run_id'] = self.run_id # library state and state copy have the same run_id
+ record['history_item_id'] = self.history_item_id
+
+ # semantic data
+ semantic_data_dict = {}
+ for k, v in target_state.semantic_data.items():
+ try:
+ semantic_data_dict[k] = pickle.dumps(v) if pickled else json.dumps(v)
+ except Exception as e:
+ semantic_data_dict['!' + k] = (str(e), str(v))
+ record['semantic_data'] = semantic_data_dict
+
+ record['description'] = target_state.description
+
+ if self.prev is not None:
+ record['prev_history_item_id'] = self.prev.history_item_id
+ else:
+ record['prev_history_item_id'] = None
+ # store the specialized class name as item_type,
+ # e.g. CallItem, ReturnItem, StatemachineStartItem when saved
+ record['item_type'] = self.__class__.__name__
+ return record
+
+
+class StateMachineStartItem(HistoryItem):
+ def __init__(self, state_machine, run_id):
+ HistoryItem.__init__(self, state_machine.root_state, run_id)
+ from rafcon.core.state_machine import StateMachine
+ self.sm_dict = StateMachine.state_machine_to_dict(state_machine)
+ self.prev = None
+ self.os_environment = dict(os.environ)
+
+ def __str__(self):
+ return "StateMachineStartItem with name %s (time: %s)" % (self.sm_dict['root_state_storage_id'], self.timestamp)
+
+ def to_dict(self, pickled=True):
+ record = HistoryItem.to_dict(self, pickled=pickled)
+ record.update(self.sm_dict)
+ record['call_type'] = 'EXECUTE'
+ record['state_name'] = 'StateMachineStartItem'
+ record['state_type'] = 'StateMachine'
+ record['path'] = ''
+ record['path_by_name'] = ''
+ record['os_environment'] = self.os_environment
+ if self.prev is not None:
+ record['prev_history_item_id'] = self.prev.history_item_id
+ else:
+ record['prev_history_item_id'] = None
+ return record
+
+
+class ScopedDataItem(HistoryItem):
+ """A abstract class to represent history items which contains the scoped data of a state
+
+ :ivar call_type: the call type of the execution step, i.e. if it refers to a container state or an execution state
+ :ivar state_for_scoped_data: the state of which the scoped data will be stored as the context data that is necessary
+ to re-execute the state
+ """
+
+ def __init__(self, state, call_type, state_for_scoped_data, child_state_input_output_data, run_id):
+ HistoryItem.__init__(self, state, run_id)
+ if call_type in CallType:
+ self.call_type_str = call_type.name
+ else:
+ raise Exception('unkown calltype, neither CONTAINER nor EXECUTE')
+ self.call_type = call_type
+ self.scoped_data = {} if state_for_scoped_data is None else copy.deepcopy(state_for_scoped_data._scoped_data)
+ self.child_state_input_output_data = copy.deepcopy(child_state_input_output_data)
+
+ def to_dict(self, pickled=True):
+ record = HistoryItem.to_dict(self, pickled=pickled)
+ scoped_data_dict = {}
+ for k, v in self.scoped_data.items():
+ try:
+ scoped_data_dict[v.name] = pickle.dumps(v.value) if pickled else json.dumps(v.value)
+ except Exception as e:
+ scoped_data_dict['!' + v.name] = (str(e), str(v.value))
+ record['scoped_data'] = scoped_data_dict
+
+ child_state_input_output_dict = {}
+ for k, v in self.child_state_input_output_data.items():
+ try:
+ child_state_input_output_dict[k] = pickle.dumps(v) if pickled else json.dumps(v)
+ except Exception as e:
+ child_state_input_output_dict['!' + k] = (str(e), str(v))
+ record['input_output_data'] = child_state_input_output_dict
+ record['call_type'] = self.call_type_str
+ return record
+
+ def __str__(self):
+ return "SingleItem %s" % (HistoryItem.__str__(self))
+
+
+class CallItem(ScopedDataItem):
+ """A history item to represent a state call
+ """
+ def __init__(self, state, call_type, state_for_scoped_data, input_data, run_id):
+ ScopedDataItem.__init__(self, state, call_type, state_for_scoped_data, input_data, run_id)
+ self.outcome = None
+
+ def __str__(self):
+ return "CallItem %s" % (ScopedDataItem.__str__(self))
+
+ def to_dict(self, pickled=True):
+ record = ScopedDataItem.to_dict(self, pickled=pickled)
+ return record
+
+
+class ReturnItem(ScopedDataItem):
+ """A history item to represent the return of a root state call
+ """
+ def __init__(self, state, call_type, state_for_scoped_data, output_data, run_id):
+ ScopedDataItem.__init__(self, state, call_type, state_for_scoped_data, output_data, run_id)
+ self.outcome = copy.deepcopy(state.final_outcome)
+
+ def __str__(self):
+ return "ReturnItem %s" % (ScopedDataItem.__str__(self))
+
+ def to_dict(self, pickled=True):
+ record = ScopedDataItem.to_dict(self, pickled=pickled)
+ if self.outcome is not None:
+ record['outcome_name'] = self.outcome.to_dict()['name']
+ record['outcome_id'] = self.outcome.to_dict()['outcome_id']
+ else:
+ record['outcome_name'] = 'None'
+ record['outcome_id'] = -1
+ return record
+
+
+class ConcurrencyItem(HistoryItem):
+ """A class to hold all the data for an invocation of several concurrent threads.
+ """
+ def __init__(self, container_state, number_concurrent_threads, run_id, consumer_manager):
+ HistoryItem.__init__(self, container_state, run_id)
+ self.execution_histories = []
+
+ from rafcon.core.execution.in_memory_execution_history import InMemoryExecutionHistory
+ from rafcon.core.execution.execution_history_factory import ExecutionHistoryFactory
+ for i in range(number_concurrent_threads):
+ execution_history = ExecutionHistoryFactory.get_execution_history(initial_prev=self,
+ consumer_manager=consumer_manager)
+ self.execution_histories.append(execution_history)
+
+ def __str__(self):
+ return "ConcurrencyItem %s" % (HistoryItem.__str__(self))
+
+ def to_dict(self, pickled=True):
+ record = HistoryItem.to_dict(self, pickled=pickled)
+ record['call_type'] = 'CONTAINER'
+ return record
+
+ def destroy(self):
+ for execution_history in self.execution_histories:
+ execution_history.destroy()
+ super(ConcurrencyItem, self).destroy()
+
+
+CallType = Enum('METHOD_NAME', 'EXECUTE CONTAINER')
\ No newline at end of file
diff --git a/source/rafcon/core/execution/execution_status.py b/source/rafcon/core/execution/execution_status.py
index bcc65b95a..d21b96eed 100644
--- a/source/rafcon/core/execution/execution_status.py
+++ b/source/rafcon/core/execution/execution_status.py
@@ -89,6 +89,7 @@ def execution_mode(self, execution_mode):
self._execution_mode = execution_mode
-StateMachineExecutionStatus = Enum('STATE_MACHINE_EXECUTION_STATUS', 'STARTED STOPPED PAUSED FINISHED '
- 'STEP_MODE FORWARD_INTO FORWARD_OVER FORWARD_OUT '
- 'BACKWARD RUN_TO_SELECTED_STATE')
+StateMachineExecutionStatus = Enum('STATE_MACHINE_EXECUTION_STATUS',
+ 'STARTED STOPPED PAUSED FINISHED '
+ 'STEP_MODE FORWARD_INTO FORWARD_OVER FORWARD_OUT '
+ 'BACKWARD RUN_TO_SELECTED_STATE RUN_SELECTED_STATE')
diff --git a/source/rafcon/core/execution/in_memory_execution_history.py b/source/rafcon/core/execution/in_memory_execution_history.py
new file mode 100644
index 000000000..b50c51edc
--- /dev/null
+++ b/source/rafcon/core/execution/in_memory_execution_history.py
@@ -0,0 +1,198 @@
+# Copyright (C) 2014-2018 DLR
+#
+# All rights reserved. This program and the accompanying materials are made
+# available under the terms of the Eclipse Public License v1.0 which
+# accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# Franz Steinmetz
+# Rico Belder
+# Sebastian Brunner
+# Sebastian Riedel
+# ried_sa
+
+"""
+.. module:: execution_history
+ :synopsis: A module for the history of one thread during state machine execution
+
+"""
+from collections import Iterable, Sized
+
+from gtkmvc3.observable import Observable
+
+from rafcon.core.execution.base_execution_history import BaseExecutionHistory
+from rafcon.utils import log
+logger = log.get_logger(__name__)
+
+
+class InMemoryExecutionHistory(BaseExecutionHistory, Observable, Iterable, Sized):
+ """A class for the history of a state machine execution
+
+ It stores all history elements in a stack wise fashion.
+
+ :ivar initial_prev: optional link to a previous element for the first element pushed into this history of
+ type :class:`rafcon.core.execution.execution_history.HistoryItem`
+ """
+
+ def __init__(self, initial_prev=None, root_state_name="", consumer_manager=None):
+ BaseExecutionHistory.__init__(self, initial_prev=initial_prev,
+ root_state_name=root_state_name,
+ consumer_manager=consumer_manager)
+ Observable.__init__(self)
+ Iterable.__init__(self)
+ Sized.__init__(self)
+ self._history_items = []
+ logger.debug("InMemoryExecutionHistory has been created")
+
+ def destroy(self):
+ """Destroy the consumer and reset all variables
+ """
+ logger.verbose("Destroy execution history!")
+ super(InMemoryExecutionHistory, self).destroy()
+ if len(self._history_items) > 0:
+ if self._history_items[0]:
+ execution_history_iterator = iter(self)
+ for history_item in execution_history_iterator:
+ history_item.destroy()
+ self.destroyed = True
+ self._history_items = None
+ self.initial_prev = None
+
+ def shutdown(self):
+ """Stop all consumers including consumer plugins
+ """
+ self.consumer_manager.stop_consumers()
+
+ def __iter__(self):
+ return iter(self._history_items)
+
+ def __len__(self):
+ return len(self._history_items)
+
+ def __getitem__(self, index):
+ return self._history_items[index]
+
+ def get_last_history_item(self):
+ """Returns the history item that was added last
+
+ :return: History item added last
+ :rtype: rafcon.core.execution.execution_history_items.HistoryItem
+ """
+ try:
+ return self[-1]
+ except IndexError: # this is the case for the very first executed state
+ return None
+
+ def _push_item(self, current_item):
+ """Push history item to the queue
+
+ :param current_item: the history item
+
+ :return: History item added
+ :rtype: rafcon.core.execution.execution_history_items.HistoryItem
+ """
+ try:
+ self._history_items.append(current_item)
+ except AttributeError:
+ if self.destroyed:
+ pass # this is fine
+ else:
+ raise
+ return current_item
+
+ def _link_history_item(self, current_item):
+ """Link the history item to the previous one
+
+ :param current_item: the history item
+ """
+ last_history_item = self.get_last_history_item()
+ if last_history_item is None:
+ current_item.prev = self.initial_prev
+ if last_history_item is not None:
+ current_item.prev = last_history_item
+ last_history_item.next = current_item
+
+ @Observable.observed
+ def push_call_history_item(self, state, call_type, state_for_scoped_data, input_data=None):
+ """Adds a new call-history-item to the history item list
+
+ A call history items stores information about the point in time where a method (entry, execute,
+ exit) of certain state was called.
+
+ :param state: the state that was called
+ :param call_type: the call type of the execution step,
+ i.e. if it refers to a container state or an execution state
+ :param state_for_scoped_data: the state of which the scoped data needs to be saved for further usages
+ (e.g. backward stepping)
+ """
+ return_item = super(InMemoryExecutionHistory, self).push_call_history_item(
+ state, call_type, state_for_scoped_data, input_data, link_and_feed_item_to_consumers=False)
+ self._link_history_item(return_item)
+ super(InMemoryExecutionHistory, self).feed_consumers(return_item)
+ return self._push_item(return_item)
+
+ @Observable.observed
+ def push_return_history_item(self, state, call_type, state_for_scoped_data, output_data=None):
+ """Adds a new return-history-item to the history item list
+
+ A return history items stores information about the point in time where a method (entry, execute,
+ exit) of certain state returned.
+
+ :param state: the state that returned
+ :param call_type: the call type of the execution step,
+ i.e. if it refers to a container state or an execution state
+ :param state_for_scoped_data: the state of which the scoped data needs to be saved for further usages (e.g.
+ backward stepping)
+ """
+ return_item = super(InMemoryExecutionHistory, self).push_return_history_item(
+ state, call_type, state_for_scoped_data, output_data, link_and_feed_item_to_consumers=False)
+ self._link_history_item(return_item)
+ super(InMemoryExecutionHistory, self).feed_consumers(return_item)
+ return self._push_item(return_item)
+
+ @Observable.observed
+ def push_concurrency_history_item(self, state, number_concurrent_threads):
+ """Adds a new concurrency-history-item to the history item list
+
+ A concurrent history item stores information about the point in time where a certain number of states is
+ launched concurrently
+ (e.g. in a barrier concurrency state).
+
+ :param state: the state that launches the state group
+ :param number_concurrent_threads: the number of states that are launched
+ """
+ return_item = super(InMemoryExecutionHistory, self).push_concurrency_history_item(
+ state, number_concurrent_threads, link_and_feed_item_to_consumers=False)
+ self._link_history_item(return_item)
+ super(InMemoryExecutionHistory, self).feed_consumers(return_item)
+ return self._push_item(return_item)
+
+ @Observable.observed
+ def push_state_machine_start_history_item(self, state_machine, run_id):
+ """Adds a new state-machine-start-history-item to the history item list
+
+ A state machine start history item stores information about the point in time where a state machine
+ started to run
+
+ :param state_machine: the state machine that started
+ :param run_id: the run id
+ """
+ return_item = super(InMemoryExecutionHistory, self).push_state_machine_start_history_item(
+ state_machine, run_id, feed_item_to_consumers=False)
+ self._history_items.append(return_item)
+ super(InMemoryExecutionHistory, self).feed_consumers(return_item)
+ return return_item
+
+ @Observable.observed
+ def pop_last_item(self):
+ """Delete and returns the last item of the history item list.
+
+ :return: History item added last
+ :rtype: rafcon.core.execution.execution_history_items.HistoryItem
+ """
+ try:
+ return self._history_items.pop()
+ except IndexError:
+ logger.error("No item left in the history item list in the execution history.")
+ return None
diff --git a/source/rafcon/core/interface.py b/source/rafcon/core/interface.py
index 26990110a..3ba012b52 100644
--- a/source/rafcon/core/interface.py
+++ b/source/rafcon/core/interface.py
@@ -23,9 +23,9 @@
def open_folder_cmd_line(query, default_path=None):
"""Queries the user for a path to open
-
+
:param str query: Query that asks the user for a specific folder path to be opened
- :param str default_path: Path to use if the user doesn't specify a path
+ :param str default_path: Path to use if the user doesn't specify a path
:return: Input path from the user or `default_path` if nothing is specified or None if path does not exist
:rtype: str
"""
@@ -94,9 +94,9 @@ def save_folder_cmd_line(query, default_name=None, default_path=None):
def show_notice(notice):
- """Shows a notice on the console that has to be acknowledged
-
- :param str notice: Notice to show to the user
+ """Shows a notice on the console that has to be acknowledged
+
+ :param str notice: Notice to show to the user
"""
input(notice)
diff --git a/source/rafcon/core/library_manager.py b/source/rafcon/core/library_manager.py
index 15bf93cca..fbb102447 100644
--- a/source/rafcon/core/library_manager.py
+++ b/source/rafcon/core/library_manager.py
@@ -36,14 +36,14 @@
class LibraryManager(Observable):
"""This class manages all libraries
- Libraries are essentially just (reusable) state machines. A set of state machines from path library_root_path
+ Libraries are essentially just (reusable) state machines. A set of state machines from path library_root_path
are mounted at point/name library_root_key in the library_manager.
With library_path and library_name the respective library state machine is found in the library manager.
Hereby a library_root_key has to be the first element of the library_path.
- The library_root_paths are handed in the config.yaml in the LIBRARY_PATHS dictionary as pairs of library_root_key
+ The library_root_paths are handed in the config.yaml in the LIBRARY_PATHS dictionary as pairs of library_root_key
and library_root_path.
The library_root_path can be relative paths and could include environment variables.
- A library is pointed on by the file system path library_os_path which again partial consists of
+ A library is pointed on by the file system path library_os_path which again partial consists of
library_root_path + library_path (partly) + library_name.
:ivar _libraries: a dictionary to hold all libraries
"""
@@ -63,6 +63,16 @@ def __init__(self):
self._loaded_libraries = {}
self._libraries_instances = {}
+ self._show_dialog = True
+
+ @property
+ def show_dialog(self):
+ return self._show_dialog
+
+ @show_dialog.setter
+ def show_dialog(self, value):
+ self._show_dialog = value
+
def prepare_destruction(self):
self.clean_loaded_libraries()
@@ -210,11 +220,11 @@ def library_root_paths(self):
def get_os_path_to_library(self, library_path, library_name, allow_user_interaction=True):
"""Find library_os_path of library
- This function retrieves the file system library_os_path of a library specified by a library_path and a
- library_name. In case the library does not exist any more at its original location, the user has to specify
+ This function retrieves the file system library_os_path of a library specified by a library_path and a
+ library_name. In case the library does not exist any more at its original location, the user has to specify
an alternative location.
- :param str library_path: The library_path of the library, that must be relative and within a library_root_path
+ :param str library_path: The library_path of the library, that must be relative and within a library_root_path
given in the config.yaml by LIBRARY_PATHS
:param str library_name: The name of the library
:param bool allow_user_interaction: Whether the user may be asked to specify library location
@@ -262,7 +272,7 @@ def get_os_path_to_library(self, library_path, library_name, allow_user_interact
regularly_found = False
new_library_os_path = None
- if allow_user_interaction:
+ if allow_user_interaction and self.show_dialog:
notice = "Cannot find library '{0}' in library_path '{1}' in any of the library root paths. " \
"Please check your library root paths configuration in config.yaml " \
"LIBRARY_PATHS and environment variable RAFCON_LIBRARY_PATH. " \
@@ -343,7 +353,7 @@ def is_library_in_libraries(self, library_path, library_name):
def get_library_path_and_name_for_os_path(self, path):
"""Generate valid library_path and library_name
- The method checks if the given os path is in the list of loaded library root paths and use respective
+ The method checks if the given os path is in the list of loaded library root paths and use respective
library root key/mounting point to concatenate the respective library_path and separate respective library_name.
:param str path: A library os path a library is situated in.
@@ -359,7 +369,7 @@ def get_library_path_and_name_for_os_path(self, path):
path_elements_without_library_root = path[len(library_root_path)+1:].split(os.sep)
library_name = path_elements_without_library_root[-1]
sub_library_path = ''
- if len(path_elements_without_library_root[:-1]):
+ if path_elements_without_library_root[:-1]:
sub_library_path = os.sep + os.sep.join(path_elements_without_library_root[:-1])
library_path = library_root_key + sub_library_path
return library_path, library_name
diff --git a/source/rafcon/core/start.py b/source/rafcon/core/start.py
index 70202481b..91d5ff2bc 100755
--- a/source/rafcon/core/start.py
+++ b/source/rafcon/core/start.py
@@ -14,7 +14,6 @@
# Michael Vilzmann
# Rico Belder
# Sebastian Brunner
-
"""
.. module: a module to enable state machine execution from the command line
:synopsis: A module to start arbitrary state machines without the GUI and several configurations options
@@ -78,11 +77,12 @@ def setup_environment():
if rafcon_library_path:
os.environ['RAFCON_LIB_PATH'] = rafcon_library_path
else:
- logger.warning("Could not find root directory of RAFCON libraries. Please specify manually using the "
- "env var RAFCON_LIB_PATH")
+ logger.warning(
+ "Could not find root directory of RAFCON libraries. Please specify manually using the "
+ "env var RAFCON_LIB_PATH")
# Install dummy _ builtin function in case i18.setup_l10n() is not called
- if sys.version_info >= (3,):
+ if sys.version_info >= (3, ):
import builtins as builtins23
else:
import __builtin__ as builtins23
@@ -104,8 +104,8 @@ def parse_state_machine_path(path):
sm_root_file = join(path, storage.STATEMACHINE_FILE_OLD)
if exists(sm_root_file):
return path
- raise argparse.ArgumentTypeError("Failed to open {0}: {1} not found in path".format(path,
- storage.STATEMACHINE_FILE))
+ raise argparse.ArgumentTypeError("Failed to open {0}: {1} not found in path".format(
+ path, storage.STATEMACHINE_FILE))
def setup_argument_parser():
@@ -117,18 +117,38 @@ def setup_argument_parser():
filesystem.create_path(default_config_path)
parser = core_singletons.argument_parser
- parser.add_argument('-o', '--open', type=parse_state_machine_path, dest='state_machine_path', metavar='path',
- nargs='+', help="specify directories of state-machines that shall be opened. The path must "
- "contain a statemachine.json file")
- parser.add_argument('-c', '--config', type=config_path, metavar='path', dest='config_path',
- default=default_config_path, nargs='?', const=default_config_path,
- help="path to the configuration file config.yaml. Use 'None' to prevent the generation of "
- "a config file and use the default configuration. Default: {0}".format(default_config_path))
+ parser.add_argument(
+ '-o',
+ '--open',
+ type=parse_state_machine_path,
+ dest='state_machine_path',
+ metavar='path',
+ nargs='+',
+ help="specify directories of state-machines that shall be opened. The path must "
+ "contain a statemachine.json file")
+ parser.add_argument(
+ '-c',
+ '--config',
+ type=config_path,
+ metavar='path',
+ dest='config_path',
+ default=default_config_path,
+ nargs='?',
+ const=default_config_path,
+ help="path to the configuration file config.yaml. Use 'None' to prevent the generation of "
+ "a config file and use the default configuration. Default: {0}".format(
+ default_config_path))
parser.add_argument('-r', '--remote', action='store_true', help="remote control mode")
- parser.add_argument('-s', '--start_state_path', metavar='path', dest='start_state_path', default=None, nargs='?',
- help="path within a state machine to the state that should be launched. The state path "
- "consists of state ids (e.g. QPOXGD/YVWJKZ whereof QPOXGD is the root state and YVWJKZ "
- "it's child state to start from).")
+ parser.add_argument(
+ '-s',
+ '--start_state_path',
+ metavar='path',
+ dest='start_state_path',
+ default=None,
+ nargs='?',
+ help="path within a state machine to the state that should be launched. The state path "
+ "consists of state ids (e.g. QPOXGD/YVWJKZ whereof QPOXGD is the root state and YVWJKZ "
+ "it's child state to start from).")
return parser
@@ -160,10 +180,14 @@ def open_state_machine(state_machine_path):
def start_state_machine(sm, start_state_path=None):
- core_singletons.state_machine_execution_engine.start(sm.state_machine_id, start_state_path=start_state_path)
+ core_singletons.state_machine_execution_engine.start(
+ sm.state_machine_id, start_state_path=start_state_path)
if reactor_required():
- sm_thread = threading.Thread(target=stop_reactor_on_state_machine_finish, args=[sm, ])
+ sm_thread = threading.Thread(
+ target=stop_reactor_on_state_machine_finish, args=[
+ sm,
+ ])
sm_thread.start()
@@ -177,7 +201,7 @@ def wait_for_state_machine_finished(state_machine):
from rafcon.core.states.execution_state import ExecutionState
if not isinstance(state_machine.root_state, ExecutionState):
- while len(state_machine.execution_histories[0]) < 1:
+ while not state_machine.root_state.final_outcome:
time.sleep(0.1)
else:
time.sleep(0.5)
@@ -212,7 +236,7 @@ def reactor_required():
return False
-def signal_handler(signal, frame):
+def signal_handler(signal, frame=None):
global _user_abort
state_machine_execution_engine = core_singletons.state_machine_execution_engine
@@ -244,7 +268,8 @@ def register_signal_handlers(callback):
signal.signal(signal_code, callback)
-def main():
+def main(optional_args=None):
+
register_signal_handlers(signal_handler)
logger.info("initialize RAFCON ... ")
@@ -255,7 +280,11 @@ def main():
logger.info("parse arguments ... ")
parser = setup_argument_parser()
- user_input = parser.parse_args()
+
+ if optional_args:
+ user_input = parser.parse_args(optional_args)
+ else:
+ user_input = parser.parse_args()
if not user_input.state_machine_path:
logger.error("You have to specify a valid state machine path")
exit(-1)
diff --git a/source/rafcon/core/state_elements/data_port.py b/source/rafcon/core/state_elements/data_port.py
index 043138be4..ebd453cf3 100644
--- a/source/rafcon/core/state_elements/data_port.py
+++ b/source/rafcon/core/state_elements/data_port.py
@@ -58,6 +58,8 @@ def __init__(self, name=None, data_type=None, default_value=None, data_port_id=N
raise NotImplementedError
super(DataPort, self).__init__(safe_init=safe_init)
self._no_type_error_exceptions = True if init_without_default_value_type_exceptions else False
+ if global_config.get_config_value("LIBRARY_RECOVERY_MODE") is True:
+ self._no_type_error_exceptions = True
self._was_forced_type = force_type
if data_port_id is None:
self._data_port_id = generate_data_port_id([])
@@ -66,8 +68,6 @@ def __init__(self, name=None, data_type=None, default_value=None, data_port_id=N
else:
self._data_port_id = data_port_id
- self._no_type_error_exceptions = False
-
if safe_init:
DataPort._safe_init(self, name, data_type, default_value, parent)
else:
@@ -266,7 +266,7 @@ def check_default_value(self, default_value, data_type=None):
else:
if not isinstance(default_value, self.data_type):
if self._no_type_error_exceptions:
- logger.warning("Handed default value '{0}' is of type '{1}' but data port data type is {2} {3}."
+ logger.error("Handed default value '{0}' is of type '{1}' but data port data type is {2} {3}."
"".format(default_value, type(default_value), data_type, self))
else:
raise TypeError("Handed default value '{0}' is of type '{1}' but data port data type is {2}"
diff --git a/source/rafcon/core/state_machine.py b/source/rafcon/core/state_machine.py
index 551f8c02c..8a824cf18 100644
--- a/source/rafcon/core/state_machine.py
+++ b/source/rafcon/core/state_machine.py
@@ -33,16 +33,14 @@
from jsonconversion.jsonobject import JSONObject
import rafcon
-from rafcon.core.execution.execution_history import ExecutionHistory, ExecutionHistoryStorage
+from rafcon.core.execution.execution_history_factory import ExecutionHistoryFactory
from rafcon.core.id_generator import generate_state_machine_id, run_id_generator
from rafcon.utils import log
from rafcon.utils.hashable import Hashable
from rafcon.utils.storage_utils import get_current_time_string
import time
-from rafcon.utils.constants import RAFCON_TEMP_PATH_BASE
from rafcon.core.config import global_config
-import os
logger = log.get_logger(__name__)
@@ -147,12 +145,20 @@ def start(self):
def join(self):
"""Wait for root state to finish execution"""
+ from rafcon.core.states.concurrency_state import ConcurrencyState
self._root_state.join()
- # execution finished, close execution history log file (if present)
- if len(self._execution_histories) > 0:
- if self._execution_histories[-1].execution_history_storage is not None:
- set_read_and_writable_for_all = global_config.get_config_value("EXECUTION_LOG_SET_READ_AND_WRITABLE_FOR_ALL", False)
- self._execution_histories[-1].execution_history_storage.close(set_read_and_writable_for_all)
+ if not global_config.get_config_value("IN_MEMORY_EXECUTION_HISTORY_ENABLE", False):
+ queue = [self.root_state]
+ while len(queue) > 0:
+ state = queue.pop(0)
+ if isinstance(state, ConcurrencyState):
+ if state.concurrency_history_item is not None:
+ state.concurrency_history_item.destroy()
+ state.concurrency_history_item = None
+ elif hasattr(state, 'states'):
+ queue.extend(state.states.values())
+ if len(self.execution_histories) > 0:
+ self.execution_histories[-1].shutdown()
from rafcon.core.states.state import StateExecutionStatus
self._root_state.state_execution_status = StateExecutionStatus.INACTIVE
@@ -225,19 +231,7 @@ def destroy_execution_histories(self):
@Observable.observed
def _add_new_execution_history(self):
- new_execution_history = ExecutionHistory()
-
- if global_config.get_config_value("EXECUTION_LOG_ENABLE", False):
- base_dir = global_config.get_config_value("EXECUTION_LOG_PATH", "%RAFCON_TEMP_PATH_BASE/execution_logs")
- if base_dir.startswith('%RAFCON_TEMP_PATH_BASE'):
- base_dir = base_dir.replace('%RAFCON_TEMP_PATH_BASE', RAFCON_TEMP_PATH_BASE)
- if not os.path.exists(base_dir):
- os.makedirs(base_dir)
- shelve_name = os.path.join(base_dir, '%s_rafcon_execution_log_%s.shelve' %
- (time.strftime('%Y-%m-%d-%H:%M:%S', time.localtime()),
- self.root_state.name.replace(' ', '-')))
- execution_history_store = ExecutionHistoryStorage(shelve_name)
- new_execution_history.set_execution_history_storage(execution_history_store)
+ new_execution_history = ExecutionHistoryFactory.get_execution_history(root_state_name=self.root_state.name)
self._execution_histories.append(new_execution_history)
return new_execution_history
@@ -305,8 +299,8 @@ def get_state_by_path(self, path, as_check=False):
def get_last_execution_log_filename(self):
if len(self._execution_histories) > 0:
for i in range(len(self._execution_histories) - 1, -1, -1):
- if self._execution_histories[i].execution_history_storage is not None:
- return self._execution_histories[i].execution_history_storage.filename
+ if self._execution_histories[i].consumer_manager.get_file_system_consumer_file_name() is not None:
+ return self._execution_histories[i].consumer_manager.get_file_system_consumer_file_name()
return None
else:
return None
diff --git a/source/rafcon/core/state_machine_manager.py b/source/rafcon/core/state_machine_manager.py
index d5bbe1434..f8fdf1716 100644
--- a/source/rafcon/core/state_machine_manager.py
+++ b/source/rafcon/core/state_machine_manager.py
@@ -48,7 +48,7 @@ def __init__(self, state_machines=None):
self.add_state_machine(state_machine)
def delete_all_state_machines(self):
- sm_ids = [sm_id for sm_id in self.state_machines]
+ sm_ids = list(self.state_machines)
for sm_id in sm_ids:
if not (sm_id == self.active_state_machine_id):
self.remove_state_machine(sm_id)
@@ -125,6 +125,19 @@ def remove_state_machine(self, state_machine_id):
removed_state_machine.destroy_execution_histories()
return removed_state_machine
+ def remove_state_machine_by_path(self, state_machine_path):
+ """ Remove an open state machine by path
+
+ :param str state_machine_path: the state machine path
+ """
+
+ state_machine_ids = []
+ for state_machine_id, state_machine in self._state_machines.items():
+ if state_machine.file_system_path == state_machine_path:
+ state_machine_ids.append(state_machine_id)
+ for state_machine_id in state_machine_ids:
+ self.remove_state_machine(state_machine_id)
+
def get_active_state_machine(self):
"""Return a reference to the active state-machine
"""
diff --git a/source/rafcon/core/states/concurrency_state.py b/source/rafcon/core/states/concurrency_state.py
index 901e3b3f6..254c658de 100644
--- a/source/rafcon/core/states/concurrency_state.py
+++ b/source/rafcon/core/states/concurrency_state.py
@@ -22,9 +22,9 @@
from gtkmvc3.observable import Observable
import rafcon.core.singleton as singleton
+from rafcon.core.config import global_config
from rafcon.core.states.container_state import ContainerState
-from rafcon.core.execution.execution_history import CallType
-from rafcon.core.execution.execution_history import CallItem, ReturnItem, ConcurrencyItem
+from rafcon.core.execution.execution_history_items import CallItem, ReturnItem, ConcurrencyItem, CallType
from rafcon.core.states.state import StateExecutionStatus
from rafcon.core.state_elements.logical_port import Outcome
@@ -40,6 +40,7 @@ def __init__(self, name=None, state_id=None, input_keys=None, output_keys=None,
scoped_variables=None, safe_init=True):
ContainerState.__init__(self, name, state_id, input_keys, output_keys, income, outcomes, states, transitions,
data_flows, start_state_id, scoped_variables, safe_init=safe_init)
+ self.concurrency_history_item = None
def run(self, *args, **kwargs):
""" The abstract run method that has to be implemented by all concurrency states.
@@ -75,6 +76,9 @@ def setup_forward_or_backward_execution(self):
else: # forward_execution
self.execution_history.push_call_history_item(self, CallType.CONTAINER, self, self.input_data)
concurrency_history_item = self.execution_history.push_concurrency_history_item(self, len(self.states))
+ # Save a reference to the concurrency_history_item here in order to be able to destruct it
+ # after the concurrency_state execution has finished
+ self.concurrency_history_item = concurrency_history_item
return concurrency_history_item
def start_child_states(self, concurrency_history_item, do_not_start_state=None):
@@ -100,14 +104,20 @@ def start_child_states(self, concurrency_history_item, do_not_start_state=None):
state.concurrency_queue_id = index
state.generate_run_id()
+ target_excution_history = None
if not self.backward_execution:
- # care for the history items; this item is only for execution visualization
- concurrency_history_item.execution_histories[index].push_call_history_item(
- state, CallType.EXECUTE, self, state.input_data)
+ # In the case that execution logging is disabled there is no concurrency_history_item
+ if concurrency_history_item:
+ # care for the history items; this item is only for execution visualization
+ concurrency_history_item.execution_histories[index].push_call_history_item(
+ state, CallType.EXECUTE, self, state.input_data)
+ target_excution_history = concurrency_history_item.execution_histories[index]
else: # backward execution
last_history_item = concurrency_history_item.execution_histories[index].pop_last_item()
assert isinstance(last_history_item, ReturnItem)
- state.start(concurrency_history_item.execution_histories[index], self.backward_execution, False)
+ target_excution_history = concurrency_history_item.execution_histories[index]
+
+ state.start(target_excution_history, self.backward_execution, False)
return concurrency_queue
@@ -130,8 +140,10 @@ def join_state(self, state, history_index, concurrency_history_item):
if not self.backward_execution:
state.concurrency_queue = None
# add the data of all child states to the scoped data and the scoped variables
- state.execution_history.push_return_history_item(state, CallType.EXECUTE, self, state.output_data)
- else:
+ if state.execution_history is not None:
+ state.execution_history.push_return_history_item(state, CallType.EXECUTE, self, state.output_data)
+ else: # backward execution case
+ # in case of a backward execution the concurrency_history_item always exists
last_history_item = concurrency_history_item.execution_histories[history_index].pop_last_item()
assert isinstance(last_history_item, CallItem)
@@ -141,7 +153,7 @@ def finalize_backward_execution(self):
:return:
"""
# backward_execution needs to be True to signal the parent container state the backward execution
- self.backward_execution = True
+ self.backward_execution = True # TODO: why is this line needed?
# pop the ConcurrencyItem as we are leaving the barrier concurrency state
last_history_item = self.execution_history.pop_last_item()
assert isinstance(last_history_item, ConcurrencyItem)
diff --git a/source/rafcon/core/states/container_state.py b/source/rafcon/core/states/container_state.py
index f2422b529..1237e19ee 100644
--- a/source/rafcon/core/states/container_state.py
+++ b/source/rafcon/core/states/container_state.py
@@ -66,7 +66,7 @@ class ContainerState(State):
def __init__(self, name=None, state_id=None, input_data_ports=None, output_data_ports=None,
income=None, outcomes=None,
states=None, transitions=None, data_flows=None, start_state_id=None,
- scoped_variables=None, safe_init=True):
+ scoped_variables=None, missing_library_meta_data=None, is_dummy=False, safe_init=True):
self._states = OrderedDict()
self._transitions = {}
@@ -78,6 +78,17 @@ def __init__(self, name=None, state_id=None, input_data_ports=None, output_data_
self._transitions_cv = Condition()
self._child_execution = False
self._start_state_modified = False
+ """
+ Dummy state machine is created only in one place and it is a ContrainerState.
+ So, it is always a ContrainerState by design.
+ """
+ self._is_dummy = is_dummy
+ """
+ In the case of a dummy state machine, we must use the same meta_data as the missing library was using.
+ Hence, we must add an additional field for this case as it is not possible to handle it in a
+ container state model when a library does not exist anymore.
+ """
+ self._missing_library_meta_data = missing_library_meta_data
State.__init__(self, name, state_id, input_data_ports, output_data_ports, income, outcomes, safe_init=safe_init)
@@ -1031,22 +1042,25 @@ def substitute_state(self, state_id, state):
act_output_data_port_by_name = {op.name: op for op in state.output_data_ports.values()}
for t in related_transitions['external']['self']:
- new_t_id = self.add_transition(state_id, t.from_outcome, state_id, t.to_outcome, t.transition_id)
- re_create_io_going_t_ids.append(new_t_id)
- assert new_t_id == t.transition_id
+ if t.transition_id not in self._transitions:
+ new_t_id = self.add_transition(state_id, t.from_outcome, state_id, t.to_outcome, t.transition_id)
+ re_create_io_going_t_ids.append(new_t_id)
+ assert new_t_id == t.transition_id
for t in related_transitions['external']['ingoing']:
- new_t_id = self.add_transition(t.from_state, t.from_outcome, state_id, t.to_outcome, t.transition_id)
- re_create_io_going_t_ids.append(new_t_id)
- assert new_t_id == t.transition_id
-
- for t in related_transitions['external']['outgoing']:
- from_outcome = act_outcome_ids_by_name.get(old_outcome_names[t.from_outcome], None)
- if from_outcome is not None:
- new_t_id = self.add_transition(state_id, from_outcome, t.to_state, t.to_outcome, t.transition_id)
+ if t.transition_id not in self._transitions:
+ new_t_id = self.add_transition(t.from_state, t.from_outcome, state_id, t.to_outcome, t.transition_id)
re_create_io_going_t_ids.append(new_t_id)
assert new_t_id == t.transition_id
+ for t in related_transitions['external']['outgoing']:
+ if t.transition_id not in self._transitions:
+ from_outcome = act_outcome_ids_by_name.get(old_outcome_names[t.from_outcome], None)
+ if from_outcome is not None:
+ new_t_id = self.add_transition(state_id, from_outcome, t.to_state, t.to_outcome, t.transition_id)
+ re_create_io_going_t_ids.append(new_t_id)
+ assert new_t_id == t.transition_id
+
for old_ip in old_input_data_ports.values():
ip = act_input_data_port_by_name.get(old_input_data_ports[old_ip.data_port_id].name, None)
if ip is not None and ip.data_type == old_input_data_ports[old_ip.data_port_id].data_type:
@@ -1635,6 +1649,10 @@ def add_state_execution_output_to_scoped_data(self, dictionary, state):
not (isinstance(value, type(None)))):
logger.error("The data type of output port {0} should be of type {1}, but is of type {2}".
format(output_name, data_port.data_type, type(value)))
+ elif value is None:
+ logger.warning(
+ "The value of output port is 'None'. It has replaced with the default value.".
+ format(output_name, data_port.data_type, type(value)))
self.scoped_data[str(output_data_port_key) + state.state_id] = \
ScopedData(data_port.name, value, type(value), state.state_id, OutputDataPort, parent=self)
@@ -2239,7 +2257,8 @@ def transitions(self, transitions):
transition.parent = self
except (ValueError, RecoveryModeException) as e:
if type(e) is RecoveryModeException:
- logger.exception("Recovery error:")
+ logger.exception("Recovery error: " + str(e))
+ logger.error("The transition is going to be deleted!")
if e.do_delete_item:
transition_ids_to_delete.append(transition.transition_id)
else:
@@ -2296,7 +2315,8 @@ def data_flows(self, data_flows):
data_flow.parent = self
except (ValueError, RecoveryModeException) as e:
if type(e) is RecoveryModeException:
- logger.error("Recovery error:")
+ logger.error("Recovery error: " + str(e))
+ logger.error("The data-flow is going to be deleted!")
if e.do_delete_item:
data_flow_ids_to_delete.append(data_flow.data_flow_id)
else:
@@ -2436,3 +2456,27 @@ def child_execution(self):
return True
else:
return False
+
+ @property
+ def is_dummy(self):
+ """Property for the _is_dummy field
+ """
+
+ return self._is_dummy
+
+ @is_dummy.setter
+ @lock_state_machine
+ def is_dummy(self, is_dummy):
+ self._is_dummy = is_dummy
+
+ @property
+ def missing_library_meta_data(self):
+ """Property for the _missing_library_meta_data field
+ """
+
+ return self._missing_library_meta_data
+
+ @missing_library_meta_data.setter
+ @lock_state_machine
+ def missing_library_meta_data(self, missing_library_meta_data):
+ self._missing_library_meta_data = missing_library_meta_data
diff --git a/source/rafcon/core/states/execution_state.py b/source/rafcon/core/states/execution_state.py
index 1e8650e73..02abe3057 100644
--- a/source/rafcon/core/states/execution_state.py
+++ b/source/rafcon/core/states/execution_state.py
@@ -31,7 +31,7 @@
from rafcon.core.state_elements.logical_port import Outcome
from rafcon.core.script import Script
from rafcon.core.states.state import StateExecutionStatus
-from rafcon.core.execution.execution_history import CallType
+from rafcon.core.execution.execution_history_items import CallType
from rafcon.core.config import global_config
from rafcon.utils import log
@@ -136,7 +136,7 @@ def run(self):
:return:
"""
- if self.is_root_state:
+ if self.is_root_state and self.execution_history is not None:
self.execution_history.push_call_history_item(self, CallType.EXECUTE, None, self.input_data)
logger.debug("Running {0}{1}".format(self, " (backwards)" if self.backward_execution else ""))
@@ -157,7 +157,7 @@ def run(self):
self.check_output_data_type()
result = self.finalize(outcome)
- if self.is_root_state:
+ if self.is_root_state and self.execution_history is not None:
self.execution_history.push_return_history_item(self, CallType.EXECUTE, None, self.output_data)
return result
except Exception as e:
diff --git a/source/rafcon/core/states/hierarchy_state.py b/source/rafcon/core/states/hierarchy_state.py
index 6dc944b25..aaf6bcf6f 100644
--- a/source/rafcon/core/states/hierarchy_state.py
+++ b/source/rafcon/core/states/hierarchy_state.py
@@ -24,10 +24,9 @@
from rafcon.core.states.container_state import ContainerState
from rafcon.core.state_elements.logical_port import Outcome
import rafcon.core.singleton as singleton
-from rafcon.core.execution.execution_history import CallItem, ReturnItem
+from rafcon.core.execution.execution_history_items import CallItem, ReturnItem, CallType
from rafcon.core.execution.execution_status import StateMachineExecutionStatus
from rafcon.core.states.state import StateExecutionStatus
-from rafcon.core.execution.execution_history import CallType
logger = log.get_logger(__name__)
@@ -43,10 +42,11 @@ class HierarchyState(ContainerState):
def __init__(self, name=None, state_id=None, input_data_ports=None, output_data_ports=None,
income=None, outcomes=None, states=None, transitions=None, data_flows=None, start_state_id=None,
- scoped_variables=None, safe_init=True):
+ scoped_variables=None, missing_library_meta_data=None, is_dummy=False, safe_init=True):
ContainerState.__init__(self, name, state_id, input_data_ports, output_data_ports, income, outcomes, states,
- transitions, data_flows, start_state_id, scoped_variables, safe_init=safe_init)
+ transitions, data_flows, start_state_id, scoped_variables, missing_library_meta_data,
+ is_dummy, safe_init=safe_init)
self.handling_execution_mode = False
self.child_state = None
@@ -73,6 +73,7 @@ def _initialize_hierarchy(self):
self.state_execution_status = StateExecutionStatus.WAIT_FOR_NEXT_STATE
if self.backward_execution:
+ # in backward execution case there always has an execution_history to exist
last_history_item = self.execution_history.pop_last_item()
assert isinstance(last_history_item, ReturnItem)
self.scoped_data = last_history_item.scoped_data
@@ -110,7 +111,7 @@ def run(self):
# print("hs2", self.name)
- self.backward_execution = False
+ self.backward_execution = False # TODO: why is this line needed?
if self.preempted:
if self.last_transition and self.last_transition.from_outcome == -2:
logger.debug("Execute preemption handling for '{0}'".format(self.child_state))
diff --git a/source/rafcon/core/states/library_state.py b/source/rafcon/core/states/library_state.py
index 3d9b6afcd..279c9ea55 100644
--- a/source/rafcon/core/states/library_state.py
+++ b/source/rafcon/core/states/library_state.py
@@ -87,6 +87,9 @@ def __init__(self, library_path=None, library_name=None, version=None, # librar
self.library_name = library_name
self.version = version
+ if global_config.get_config_value("RAISE_ERROR_ON_MISSING_LIBRARY_STATES", False):
+ allow_user_interaction = False
+
lib_os_path, new_library_path, new_library_name = \
library_manager.get_os_path_to_library(library_path, library_name, allow_user_interaction)
self.lib_os_path = lib_os_path
diff --git a/source/rafcon/core/states/state.py b/source/rafcon/core/states/state.py
index a8fa693de..06639e8d1 100644
--- a/source/rafcon/core/states/state.py
+++ b/source/rafcon/core/states/state.py
@@ -352,7 +352,12 @@ def get_previously_executed_state(self):
:return: The last state in the execution history
"""
- return self.execution_history.get_last_history_item().prev.state_reference
+ previous_state = self.execution_history.get_last_history_item().prev.state_reference
+ if not previous_state:
+ logger.warning(
+ "The In-Memory-Execution-History is disabled. "
+ "Thus, then previously executed state cannot be retrieved.")
+ return previous_state
# ---------------------------------------------------------------------------------------------
# ------------------------------- input/output data handling ----------------------------------
diff --git a/source/rafcon/core/storage/storage.py b/source/rafcon/core/storage/storage.py
index 0aa722093..429d848c8 100644
--- a/source/rafcon/core/storage/storage.py
+++ b/source/rafcon/core/storage/storage.py
@@ -35,11 +35,14 @@
from rafcon.utils import storage_utils
from rafcon.utils import log
from rafcon.utils.timer import measure_time
+from rafcon.utils.vividict import Vividict
from rafcon.core.custom_exceptions import LibraryNotFoundException
from rafcon.core.constants import DEFAULT_SCRIPT_PATH
from rafcon.core.config import global_config
from rafcon.core.state_machine import StateMachine
+from rafcon.core.state_elements.logical_port import Outcome
+from rafcon.core.state_elements.data_port import InputDataPort, OutputDataPort
logger = log.get_logger(__name__)
@@ -71,7 +74,7 @@ def remove_obsolete_folders(states, path):
This function removes all folders in the file system folder `path` that do not belong to the states given by
`states`.
-
+
:param list states: the states that should reside in this very folder
:param str path: the file system path to be checked for valid folders
"""
@@ -134,7 +137,7 @@ def clean_path(base_path):
"""
This function cleans a file system path in terms of removing all not allowed characters of each path element.
A path element is an element of a path between the path separator of the operating system.
-
+
:param base_path: the path to be cleaned
:return: the clean path
"""
@@ -311,7 +314,7 @@ def load_state_machine_from_path(base_path, state_machine_id=None):
state_machine_dict['used_rafcon_version'], rafcon.__version__)
rafcon_older_than_sm_version = "You are trying to load a state machine that was stored with an newer " \
"version of RAFCON ({0}) than the one you are using ({1}).".format(
- state_machine_dict['used_rafcon_version'], rafcon.__version__)
+ state_machine_dict['used_rafcon_version'], rafcon.__version__)
note_about_possible_incompatibility = "The state machine will be loaded with no guarantee of success."
if active_rafcon_version[0] > previously_used_rafcon_version[0]:
@@ -350,6 +353,7 @@ def load_state_machine_from_path(base_path, state_machine_id=None):
dirty_states = []
state_machine.root_state = load_state_recursively(parent=state_machine, state_path=root_state_path,
dirty_states=dirty_states)
+ reconnect_data_flow(state_machine)
if state_machine.root_state is None:
return # a corresponding exception has been handled with a proper error log in load_state_recursively
if len(dirty_states) > 0:
@@ -369,6 +373,57 @@ def load_state_machine_from_path(base_path, state_machine_id=None):
return state_machine
+def reconnect_data_flow(state_machine):
+ queue = [state_machine.root_state]
+ while len(queue) > 0:
+ state = queue.pop(0)
+ if hasattr(state, 'is_dummy') and state.is_dummy:
+ same_level_states = [state.parent]
+ for same_level_state in state.parent.states.values():
+ if same_level_state.state_id != state.state_id:
+ same_level_states.append(same_level_state)
+ for same_level_state in same_level_states:
+ for data_flow in state.parent.data_flows.values():
+ data_type = int
+ default_value = None
+ if data_flow.from_state == state.state_id and data_flow.to_state == same_level_state.state_id:
+ if data_flow.to_key in same_level_state.input_data_ports:
+ data_type = same_level_state.input_data_ports[data_flow.to_key].data_type
+ default_value = same_level_state.input_data_ports[data_flow.to_key].default_value
+ elif data_flow.to_key in same_level_state.output_data_ports:
+ data_type = same_level_state.output_data_ports[data_flow.to_key].data_type
+ default_value = same_level_state.output_data_ports[data_flow.to_key].default_value
+ elif data_flow.to_key in same_level_state.scoped_variables:
+ data_type = same_level_state.scoped_variables[data_flow.to_key].data_type
+ default_value = same_level_state.scoped_variables[data_flow.to_key].default_value
+ else:
+ logger.warning("The data flow type could not be found. It is set to 'int'.")
+ state.output_data_ports[data_flow.from_key] = OutputDataPort('output_' + str(len(state.output_data_ports)),
+ data_type,
+ default_value,
+ data_flow.from_key,
+ state)
+ elif data_flow.from_state == same_level_state.state_id and data_flow.to_state == state.state_id:
+ if data_flow.from_key in same_level_state.input_data_ports:
+ data_type = same_level_state.input_data_ports[data_flow.from_key].data_type
+ default_value = same_level_state.input_data_ports[data_flow.from_key].default_value
+ elif data_flow.from_key in same_level_state.output_data_ports:
+ data_type = same_level_state.output_data_ports[data_flow.from_key].data_type
+ default_value = same_level_state.output_data_ports[data_flow.from_key].default_value
+ elif data_flow.from_key in same_level_state.scoped_variables:
+ data_type = same_level_state.scoped_variables[data_flow.from_key].data_type
+ default_value = same_level_state.scoped_variables[data_flow.from_key].default_value
+ else:
+ logger.warning("The data flow type could not be found. It is set to 'int'.")
+ state.input_data_ports[data_flow.to_key] = InputDataPort('input_' + str(len(state.input_data_ports)),
+ data_type,
+ default_value,
+ data_flow.to_key,
+ state)
+ elif hasattr(state, 'states'):
+ queue.extend(state.states.values())
+
+
def load_state_from_path(state_path):
"""Loads a state from a given path
@@ -378,6 +433,14 @@ def load_state_from_path(state_path):
return load_state_recursively(parent=None, state_path=state_path)
+def get_core_data_path(state_path):
+ return os.path.join(state_path, FILE_NAME_CORE_DATA)
+
+
+def get_meta_data_path(state_path):
+ return os.path.join(state_path, FILE_NAME_META_DATA)
+
+
def load_state_recursively(parent, state_path=None, dirty_states=[]):
"""Recursively loads the state
@@ -391,26 +454,42 @@ def load_state_recursively(parent, state_path=None, dirty_states=[]):
from rafcon.core.states.execution_state import ExecutionState
from rafcon.core.states.container_state import ContainerState
from rafcon.core.states.hierarchy_state import HierarchyState
+ from rafcon.core.singleton import library_manager
- path_core_data = os.path.join(state_path, FILE_NAME_CORE_DATA)
+ path_core_data = get_core_data_path(state_path)
+ path_meta_data = get_meta_data_path(state_path)
logger.debug("Load state recursively: {0}".format(str(state_path)))
- # TODO: Should be removed with next minor release
- if not os.path.exists(path_core_data):
- path_core_data = os.path.join(state_path, FILE_NAME_CORE_DATA_OLD)
-
try:
state_info = load_data_file(path_core_data)
except ValueError as e:
logger.exception("Error while loading state data: {0}".format(e))
return
except LibraryNotFoundException as e:
+ if global_config.get_config_value("RAISE_ERROR_ON_MISSING_LIBRARY_STATES", False) or not library_manager.show_dialog:
+ raise
logger.error("Library could not be loaded: {0}\n"
"Skipping library and continuing loading the state machine".format(e))
state_info = storage_utils.load_objects_from_json(path_core_data, as_dict=True)
+ missing_library_meta_data = None
+ if os.path.exists(path_meta_data):
+ missing_library_meta_data = Vividict(storage_utils.load_objects_from_json(path_meta_data))
state_id = state_info["state_id"]
- dummy_state = HierarchyState(LIBRARY_NOT_FOUND_DUMMY_STATE_NAME, state_id=state_id)
+ outcomes = {outcome['outcome_id']: Outcome(outcome['outcome_id'], outcome['name']) for outcome in state_info["outcomes"].values()}
+ dummy_state = HierarchyState(LIBRARY_NOT_FOUND_DUMMY_STATE_NAME,
+ state_id=state_id,
+ outcomes=outcomes,
+ is_dummy=True,
+ missing_library_meta_data=missing_library_meta_data)
+ library_name = state_info['library_name']
+ path_parts = os.path.join(state_info['library_path'], library_name).split(os.sep)
+ dummy_state.description = 'The Missing Library Path: %s\nThe Missing Library Name: %s\n\n' % (state_info['library_path'], library_name)
+ from rafcon.core.singleton import library_manager
+ if path_parts[0] in library_manager.library_root_paths:
+ dummy_state.description += 'The Missing Library OS Path: %s' % os.path.join(library_manager.library_root_paths[path_parts[0]], *path_parts[1:])
+ else:
+ dummy_state.description += 'The missing library was located in the missing library root "%s"' % path_parts[0]
# set parent of dummy state
if isinstance(parent, ContainerState):
parent.add_state(dummy_state, storage_load=True)
@@ -460,24 +539,21 @@ def load_state_recursively(parent, state_path=None, dirty_states=[]):
child_state = load_state_recursively(state, child_state_path, dirty_states)
if not child_state:
return None
- if child_state.name is LIBRARY_NOT_FOUND_DUMMY_STATE_NAME:
- one_of_my_child_states_not_found = True
- if one_of_my_child_states_not_found:
- # omit adding transitions and data flows in this case
- pass
- else:
- # Now we can add transitions and data flows, as all child states were added
- if isinstance(state_info, tuple):
- # safe version
- # state.transitions = transitions
- # state.data_flows = data_flows
+ # Now we can add transitions and data flows, as all child states were added
+ if isinstance(state_info, tuple):
+ safe_init = global_config.get_config_value("LOAD_SM_WITH_CHECKS", True)
+ if safe_init:
+ # this will trigger all validity checks the state machine
+ state.transitions = transitions
+ else:
state._transitions = transitions
- for _, transition in state.transitions.items():
- transition._parent = ref(state)
state._data_flows = data_flows
- for _, data_flow in state.data_flows.items():
- data_flow._parent = ref(state)
+ for _, transition in state.transitions.items():
+ transition._parent = ref(state)
+ state._data_flows = data_flows
+ for _, data_flow in state.data_flows.items():
+ data_flow._parent = ref(state)
state.file_system_path = state_path
@@ -503,7 +579,7 @@ def limit_text_max_length(text, max_length, separator='_'):
"""
Limits the length of a string. The returned string will be the first `max_length/2` characters of the input string
plus a separator plus the last `max_length/2` characters of the input string.
-
+
:param text: the text to be limited
:param max_length: the maximum length of the output string
:param separator: the separator between the first "max_length"/2 characters of the input string and
@@ -570,3 +646,37 @@ def get_storage_id_for_state(state):
return limit_text_to_be_path_element(state.name, max_length) + ID_NAME_DELIMITER + state.state_id
else:
return state.state_id
+
+
+def find_library_dependencies_via_grep(library_root_path, library_path=None, library_name=None):
+ """ Find the dependencies of a library via grep
+
+ :param str library_root_path: the library root path
+ :param str library_path: the library path
+ :param str library_name: the library name
+
+ :rtype list(str)
+ :return: library dependency paths
+ """
+
+ library_dependency_paths = []
+ command_library_path = 'grep -r -l -E \'"library_path": "%s"|"library_path": "%s/(.*)"\' --include \\*.json %s' % (library_path, library_path, library_root_path)
+ command_library_name = 'grep -r -l \'"library_name": "%s"\' --include \\*.json %s' % (library_name, library_root_path)
+ if library_path is None and library_name is None:
+ return library_dependency_paths
+ elif library_path is None and library_name is not None:
+ final_findings = set(os.popen(command_library_name).read().splitlines())
+ elif library_path is not None and library_name is None:
+ final_findings = set(os.popen(command_library_path).read().splitlines())
+ else:
+ path_findings = set(os.popen(command_library_path).read().splitlines())
+ name_findings = set(os.popen(command_library_name).read().splitlines())
+ final_findings = path_findings.intersection(name_findings)
+ for library_dependency_path in final_findings:
+ parent = library_dependency_path
+ while True:
+ parent = os.path.dirname(parent)
+ if os.path.exists(os.path.join(parent, 'statemachine.json')):
+ break
+ library_dependency_paths.append(parent)
+ return set(library_dependency_paths)
\ No newline at end of file
diff --git a/source/rafcon/gui/config.py b/source/rafcon/gui/config.py
index 6307d43c6..611ee4c71 100644
--- a/source/rafcon/gui/config.py
+++ b/source/rafcon/gui/config.py
@@ -90,8 +90,8 @@ def configure_gtk(self):
from gi.repository import Gtk
settings = Gtk.Settings.get_default()
if settings:
- settings.set_property("gtk-enable-animations", True)
settings.set_property("gtk-theme-name", theme_name)
+ settings.set_property("gtk-enable-animations", True)
settings.set_property("gtk-application-prefer-dark-theme", dark_theme)
Gtk.Window.set_default_icon_name("rafcon" if dark_theme else "rafcon-light")
@@ -122,6 +122,8 @@ def configure_colors(self):
for line in lines:
match = re.match("\s*@define-color (\w*) (#[\w]{3,6})", line)
if match:
+ # css colors are mapped to capital-case color names
+ # these colors can then be used in the colors definition file for the gaphas colors (e.g., colors.json)
color_name = match.group(1).upper()
color_code = match.group(2)
self.colors[color_name] = color_code
diff --git a/source/rafcon/gui/controllers/execution_history.py b/source/rafcon/gui/controllers/execution_history.py
index a61fad3a6..44694b122 100644
--- a/source/rafcon/gui/controllers/execution_history.py
+++ b/source/rafcon/gui/controllers/execution_history.py
@@ -18,6 +18,7 @@
.. module:: execution_history
:synopsis: A module holding a controller for the ExecutionHistoryView holding information about the
execution history in a execution tree
+ :noindex:
"""
@@ -33,10 +34,10 @@
import rafcon
from rafcon.core.state_machine_manager import StateMachineManager
-from rafcon.core.execution.execution_history import ConcurrencyItem, CallItem, ScopedDataItem, HistoryItem
+from rafcon.core.execution.execution_history_items import HistoryItem, StateMachineStartItem, ScopedDataItem, CallItem, \
+ ConcurrencyItem, CallType
from rafcon.core.singleton import state_machine_execution_engine
from rafcon.core.execution.execution_status import StateMachineExecutionStatus
-from rafcon.core.execution.execution_history import CallType, StateMachineStartItem
from rafcon.gui.controllers.utils.extended_controller import ExtendedController
from rafcon.gui.models.state_machine_manager import StateMachineManagerModel
@@ -95,6 +96,10 @@ def register_view(self, view):
def open_selected_history_separately(self, widget, event=None):
model, row = self.history_tree.get_selection().get_selected()
+ if not row:
+ logger.info("Now execution run is selected. "
+ "Please select an execution run that should be opened externally")
+ return
item_path = self.history_tree_store.get_path(row)
selected_history_item = model[row][self.HISTORY_ITEM_STORAGE_ID]
@@ -120,19 +125,26 @@ def open_selected_history_separately(self, widget, event=None):
"The external execution history viewer can only open finished executions.")
return
- if execution_history.execution_history_storage and execution_history.execution_history_storage.filename:
+ if execution_history.consumer_manager.file_system_consumer_exists and \
+ execution_history.consumer_manager.get_file_system_consumer_file_name():
from rafcon.gui.utils.shell_execution import execute_command_in_process
gui_path = path.dirname(path.dirname(path.realpath(__file__)))
source_path = path.dirname(path.dirname(gui_path))
viewer_path = path.join(gui_path, "execution_log_viewer.py")
# TODO run in fully separate process but from here to use the option for selection synchronization via dict
- cmd = "{path} {filename} {run_id}" \
- "".format(path=viewer_path, filename=execution_history.execution_history_storage.filename,
+ import sys
+ if sys.version_info[0] == 2:
+ python_version = "python2"
+ else:
+ python_version = "python3"
+ cmd = "{python_version} {path} '{filename}' '{run_id}'" \
+ "".format(python_version=python_version, path=viewer_path,
+ filename=execution_history.consumer_manager.get_file_system_consumer_file_name(),
run_id=run_id)
execute_command_in_process(cmd, shell=True, cwd=source_path, logger=logger)
else:
- logger.info("Set EXECUTION_LOG_ENABLE to True in your config to activate execution file logging and to use "
- "the external execution history viewer.")
+ logger.info("Set FILE_SYSTEM_EXECUTION_HISTORY_ENABLE to True in your config in order to "
+ "activate execution file logging and to use the external execution history viewer.")
def append_string_to_menu(self, popup_menu, menu_item_string):
final_string = menu_item_string
diff --git a/source/rafcon/gui/controllers/global_variable_manager.py b/source/rafcon/gui/controllers/global_variable_manager.py
index 86a184dec..ca4c1b40a 100644
--- a/source/rafcon/gui/controllers/global_variable_manager.py
+++ b/source/rafcon/gui/controllers/global_variable_manager.py
@@ -18,6 +18,7 @@
.. module:: global_variable_manager
:synopsis: A module that holds the controller to access the GlobalVariableManager by a GUI based on the
GlobalVariableEditorView.
+ :noindex:
"""
diff --git a/source/rafcon/gui/controllers/library_tree.py b/source/rafcon/gui/controllers/library_tree.py
index 71985e89a..e5ba7ebc0 100644
--- a/source/rafcon/gui/controllers/library_tree.py
+++ b/source/rafcon/gui/controllers/library_tree.py
@@ -29,6 +29,7 @@
from functools import partial
from rafcon.core.states.library_state import LibraryState
+from rafcon.core.storage import storage
from rafcon.gui.config import global_gui_config
from rafcon.gui.runtime_config import global_runtime_config
from rafcon.gui.controllers.utils.extended_controller import ExtendedController
@@ -37,7 +38,8 @@
from rafcon.gui.helpers.text_formatting import format_folder_name_human_readable
import rafcon.gui.singleton as gui_singletons
from rafcon.gui.utils import constants
-from rafcon.gui.utils.dialog import RAFCONButtonDialog
+from rafcon.gui.utils.dialog import RAFCONButtonDialog, RAFCONInputDialog
+from rafcon.gui.interface import open_folder
from rafcon.utils import log
logger = log.get_logger(__name__)
@@ -52,12 +54,22 @@ class LibraryTreeController(ExtendedController):
TOOL_TIP_STORAGE_ID = 3
LIB_KEY_STORAGE_ID = 4
- def __init__(self, model, view):
+ def __init__(self, model, view, find_usages=False):
assert isinstance(model, LibraryManagerModel)
assert isinstance(view, Gtk.TreeView)
ExtendedController.__init__(self, model, view)
self.tree_store = Gtk.TreeStore(GObject.TYPE_STRING, GObject.TYPE_PYOBJECT, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING)
- view.set_model(self.tree_store)
+ self.filter = self.tree_store.filter_new()
+ self.filter_value = ''
+ self.find_usages = find_usages
+
+ if find_usages:
+ self.filter.set_visible_func(self._library_usages_filter)
+ self.usages = None
+ else:
+ self.filter.set_visible_func(self._search_filter)
+
+ view.set_model(self.filter)
view.set_tooltip_column(3)
# Gtk TODO: solve via Gtk.TargetList? https://python-gtk-3-tutorial.readthedocs.io/en/latest/drag_and_drop.html
@@ -86,8 +98,12 @@ def generate_right_click_menu(self, kind='library'):
menu.append(create_menu_item("Open", constants.BUTTON_OPEN, self.open_button_clicked))
menu.append(create_menu_item("Open and run", constants.BUTTON_START, self.open_run_button_clicked))
menu.append(Gtk.SeparatorMenuItem())
+ menu.append(create_menu_item("Rename library", constants.BUTTON_RENAME,
+ self.menu_item_rename_libraries_or_root_clicked))
menu.append(create_menu_item("Remove library", constants.BUTTON_DEL,
self.menu_item_remove_libraries_or_root_clicked))
+ menu.append(create_menu_item("Relocate library", constants.BUTTON_RELOCATE,
+ self.menu_item_relocate_libraries_or_root_clicked))
sub_menu_item, sub_menu = append_sub_menu_to_parent_menu("Substitute as library", menu,
constants.BUTTON_REFR)
@@ -104,15 +120,25 @@ def generate_right_click_menu(self, kind='library'):
partial(self.substitute_as_template_clicked, keep_name=True)))
sub_menu.append(create_menu_item("Take name from Library", constants.BUTTON_EXCHANGE,
partial(self.substitute_as_template_clicked, keep_name=False)))
+
+ menu.append(create_menu_item("Find usages in libraries", constants.ICON_FIND_USAGES,
+ self.menu_item_find_usages_clicked))
+
elif kind in ['library root', 'library tree']:
- menu.append(create_menu_item("Add library root", constants.BUTTON_DEL,
+ menu.append(create_menu_item("Add library root", constants.BUTTON_NEW,
self.menu_item_add_library_root_clicked))
if kind == 'library root':
+ menu.append(create_menu_item("Rename library root", constants.BUTTON_RENAME,
+ self.menu_item_rename_libraries_or_root_clicked))
menu.append(create_menu_item("Remove library root", constants.BUTTON_DEL,
self.menu_item_remove_libraries_or_root_clicked))
+ menu.append(create_menu_item("Relocate library root", constants.BUTTON_RELOCATE,
+ self.menu_item_relocate_libraries_or_root_clicked))
elif kind == 'libraries':
menu.append(create_menu_item("Remove libraries", constants.BUTTON_DEL,
self.menu_item_remove_libraries_or_root_clicked))
+ menu.append(create_menu_item("Relocate libraries", constants.BUTTON_RELOCATE,
+ self.menu_item_relocate_libraries_or_root_clicked))
return menu
@@ -129,6 +155,19 @@ def mouse_click(self, widget, event=None):
self.view.expand_to_path(state_row_path)
return False
self.open_button_clicked(None)
+
+ if self.find_usages:
+ state_machine_model = gui_singletons.state_machine_manager_model.get_selected_state_machine_model()
+ selected_states = []
+ queue = [state_machine_model.root_state]
+ while len(queue) > 0:
+ state = queue.pop(0)
+ if hasattr(state.state, 'lib_os_path') and state.state.library_path == self.filter_value[0] and state.state.library_name == self.filter_value[1]:
+ selected_states.append(state)
+ elif hasattr(state, 'states'):
+ queue.extend(state.states.values())
+ state_machine_model.selection.set(selected_states)
+
return True
# Single right click
@@ -186,7 +225,8 @@ def redo_expansion_state(self):
self.view.expand_to_path(library_row_path)
# print(library_path)
except (TypeError, KeyError):
- logger.warning("expansion state of library tree could not be re-done")
+ pass
+ # logger.warning("expansion state of library tree could not be re-done")
def update(self):
self.store_expansion_state()
@@ -281,7 +321,10 @@ def select_library_tree_element_of_lib_tree_path(self, lib_tree_path):
if state_row_path is not None:
self.view.expand_to_path(state_row_path)
self.view.scroll_to_cell(state_row_path, None)
- self.view.get_selection().select_iter(library_state_row_iter)
+ # Important: do not use select_iter() here!
+ # The iters of the view (whose model is the TreeModelFilter) won't match
+ # the iters stored in library_row_iter_dict_by_library_path as those directly point to the TreeModel
+ self.view.get_selection().select_path(state_row_path)
self.view.grab_focus()
def select_library_tree_element_of_library_state_model(self, state_m):
@@ -349,6 +392,51 @@ def menu_item_add_library_root_clicked(self, widget):
global_config.save_configuration()
self.model.library_manager.refresh_libraries()
+ def menu_item_rename_libraries_or_root_clicked(self, menu_item):
+ """Rename library after request second confirmation"""
+
+ import rafcon.gui.helpers.state_machine as gui_helper_state_machine
+ menu_item_text = self.get_menu_item_text(menu_item)
+ logger.info("Rename item '{0}' pressed.".format(menu_item_text))
+ model, path = self.view.get_selection().get_selected()
+ if path:
+ tree_m_row = self.filter[path]
+ library_os_path, library_path, library_name, item_key = self.extract_library_properties_from_selected_row()
+ library_file_system_path = library_os_path
+ if "root" in menu_item_text:
+ button_texts = [menu_item_text + "from tree and config", "Cancel"]
+ partial_message = "This will remove the library root from your configuration (config.yaml)."
+ else:
+ button_texts = [menu_item_text, "Cancel"]
+ partial_message = "This folder will be renamed on the hard drive! Do you really want to do that?"
+ message_string = "You have chosen to {2} with " \
+ "\n\nlibrary tree path: {0}" \
+ "\n\nphysical path: {1}\n\n\n" \
+ "{3}" \
+ "".format(os.path.join(self.convert_if_human_readable(tree_m_row[self.LIB_PATH_STORAGE_ID]), item_key),
+ library_file_system_path,
+ menu_item_text.lower(),
+ partial_message)
+ width = 8 * len("physical path: " + library_file_system_path)
+ dialog = RAFCONInputDialog(message_string,
+ button_texts,
+ message_type=Gtk.MessageType.QUESTION,
+ parent=self.get_root_window(),
+ width=min(width, 1400))
+ dialog.set_entry_text(item_key)
+ response_id = dialog.run()
+ new_name = dialog.get_entry_text()
+ dialog.destroy()
+ parent_library_os_path = os.path.abspath(os.path.join(library_os_path, os.pardir))
+ new_library_os_path = os.path.join(parent_library_os_path, new_name)
+ if response_id == 1:
+ if "root" in menu_item_text:
+ gui_helper_state_machine.rename_library_root(tree_m_row[self.LIB_KEY_STORAGE_ID], new_name, logger)
+ else:
+ gui_helper_state_machine.rename_library(library_os_path, new_library_os_path, library_path, library_name, new_name, logger)
+ return True
+ return False
+
def menu_item_remove_libraries_or_root_clicked(self, menu_item):
"""Removes library from hard drive after request second confirmation"""
@@ -358,7 +446,7 @@ def menu_item_remove_libraries_or_root_clicked(self, menu_item):
model, path = self.view.get_selection().get_selected()
if path:
# Second confirmation to delete library
- tree_m_row = self.tree_store[path]
+ tree_m_row = self.filter[path]
library_os_path, library_path, library_name, item_key = self.extract_library_properties_from_selected_row()
# assert isinstance(tree_m_row[self.ITEM_STORAGE_ID], str)
library_file_system_path = library_os_path
@@ -368,11 +456,11 @@ def menu_item_remove_libraries_or_root_clicked(self, menu_item):
partial_message = "This will remove the library root from your configuration (config.yaml)."
else:
button_texts = [menu_item_text, "Cancel"]
- partial_message = "This folder will be removed from hard drive! You really wanna do that?"
+ partial_message = "This folder will be removed from your hard drive! Do you really want to do that?"
- message_string = "You choose to {2} with " \
+ message_string = "You have chosen to {2} with " \
"\n\nlibrary tree path: {0}" \
- "\n\nphysical path: {1}.\n\n\n"\
+ "\n\nphysical path: {1}\n\n\n" \
"{3}" \
"".format(os.path.join(self.convert_if_human_readable(tree_m_row[self.LIB_PATH_STORAGE_ID]),
item_key),
@@ -411,6 +499,38 @@ def menu_item_remove_libraries_or_root_clicked(self, menu_item):
return True
return False
+ def menu_item_relocate_libraries_or_root_clicked(self, menu_item):
+ """Relocate library after request second confirmation"""
+
+ import rafcon.gui.helpers.state_machine as gui_helper_state_machine
+ menu_item_text = self.get_menu_item_text(menu_item)
+ model, path = self.view.get_selection().get_selected()
+ if path:
+ tree_m_row = self.filter[path]
+ library_os_path, library_path, library_name, item_key = self.extract_library_properties_from_selected_row()
+ new_directory = open_folder('Select the new directory')
+ if new_directory:
+ if 'root' in menu_item_text:
+ gui_helper_state_machine.relocate_library_root(tree_m_row[self.LIB_KEY_STORAGE_ID], new_directory, logger)
+ elif 'libraries' in menu_item_text:
+ gui_helper_state_machine.relocate_libraries(library_os_path.replace('[source]:\n', ''), item_key, new_directory, logger)
+ else:
+ gui_helper_state_machine.relocate_library(library_os_path, library_path, library_name, new_directory, logger)
+ return True
+ return False
+
+ def menu_item_find_usages_clicked(self, widget):
+ library_os_path, library_path, library_name, item_key = self.extract_library_properties_from_selected_row()
+ library_usages_controller = gui_singletons.main_window_controller.get_controller('library_usages_controller')
+ library_usages_controller.filter_value = [library_path, library_name]
+ usages = []
+ for root in library_usages_controller.model.library_manager.library_root_paths.values():
+ usages.extend(storage.find_library_dependencies_via_grep(root, library_usages_controller.filter_value[0], library_usages_controller.filter_value[1]))
+ library_usages_controller.usages = usages
+ library_usages_controller.filter.refilter()
+ library_usages_controller.view.expand_all()
+ gui_singletons.main_window_controller.upper_notebook.set_current_page(3)
+
def substitute_as_library_clicked(self, widget, keep_name=True):
import rafcon.gui.helpers.state_machine as gui_helper_state_machine
gui_helper_state_machine.substitute_selected_state(self._get_selected_library_state(), as_template=False,
@@ -421,6 +541,7 @@ def substitute_as_template_clicked(self, widget, keep_name=True):
gui_helper_state_machine.substitute_selected_state(self._get_selected_library_state(), as_template=True,
keep_name=keep_name)
+
def extract_library_properties_from_selected_row(self):
""" Extracts properties library_os_path, library_path, library_name and tree_item_key from tree store row """
(model, row) = self.view.get_selection().get_selected()
@@ -452,3 +573,39 @@ def _get_selected_library_state(self):
self.convert_if_human_readable(str(library_path)) + "/" + str(item_key)))
library_name = library_os_path.split(os.path.sep)[-1]
return LibraryState(library_path, library_name, "0.1", format_folder_name_human_readable(library_name))
+
+ def _search_filter(self, model, iter, data):
+ if not self.filter_value or self.filter_value in model.get_value(iter, self.ID_STORAGE_ID).lower():
+ return True
+ else:
+ parent_iter = model.iter_parent(iter)
+ while parent_iter is not None:
+ if self.filter_value in model.get_value(parent_iter, self.ID_STORAGE_ID).lower():
+ return True
+ parent_iter = model.iter_parent(parent_iter)
+ queue = [iter]
+ while len(queue) > 0:
+ node_iter = queue.pop(0)
+ if model.iter_has_child(node_iter):
+ for i in range(model.iter_n_children(node_iter)):
+ queue.append(model.iter_nth_child(node_iter, i))
+ if self.filter_value in model.get_value(node_iter, self.ID_STORAGE_ID).lower():
+ self.view.expand_all()
+ return True
+ return False
+
+ def _library_usages_filter(self, model, iter, data):
+ if self.filter_value:
+ if not model.iter_has_child(iter):
+ return model.get_value(iter, self.ITEM_STORAGE_ID) in self.usages
+ else:
+ queue = [iter]
+ while len(queue) > 0:
+ node_iter = queue.pop(0)
+ if model.iter_has_child(node_iter):
+ for i in range(model.iter_n_children(node_iter)):
+ queue.append(model.iter_nth_child(node_iter, i))
+ else:
+ if model.get_value(node_iter, self.ITEM_STORAGE_ID) in self.usages:
+ return True
+ return False
\ No newline at end of file
diff --git a/source/rafcon/gui/controllers/main_window.py b/source/rafcon/gui/controllers/main_window.py
index 52a248455..9110f61fc 100644
--- a/source/rafcon/gui/controllers/main_window.py
+++ b/source/rafcon/gui/controllers/main_window.py
@@ -87,6 +87,8 @@ def __init__(self, state_machine_manager_model, view):
# shortcut manager
self.shortcut_manager = ShortcutManager(view['main_window'])
+ self.upper_notebook = view['upper_notebook']
+
######################################################
# debug console
######################################################
@@ -101,6 +103,13 @@ def __init__(self, state_machine_manager_model, view):
library_controller = LibraryTreeController(self.library_manager_model, view.library_tree)
self.add_controller('library_controller', library_controller)
+ ######################################################
+ # library usages tree
+ ######################################################
+ library_usages_controller = LibraryTreeController(self.library_manager_model, view.library_usages_tree, True)
+ self.add_controller('library_usages_controller', library_usages_controller)
+
+
######################################################
# state icons
######################################################
@@ -222,6 +231,14 @@ def destroy(self):
def update_widget_runtime_config(widget, event, name):
global_runtime_config.store_widget_properties(widget, name)
+ def update_search_bar_visibility(self, widget, event):
+ state_machine_search = self.view['state_machine_search']
+ state_machine_search.set_visible(widget.get_active())
+ if widget.get_active():
+ state_machine_search.grab_focus()
+ else:
+ state_machine_search.set_text('')
+
def register_view(self, view):
super(MainWindowController, self).register_view(view)
self.register_actions(self.shortcut_manager)
@@ -257,6 +274,8 @@ def register_view(self, view):
self.connect_button_to_function('button_start_shortcut', "toggled", self.on_button_start_shortcut_toggled)
self.connect_button_to_function('button_stop_shortcut', "clicked", self.on_button_stop_shortcut_clicked)
self.connect_button_to_function('button_pause_shortcut', "toggled", self.on_button_pause_shortcut_toggled)
+ self.connect_button_to_function('button_run_this_state_shortcut', "clicked",
+ self.on_button_start_this_state_shortcut_clicked)
self.connect_button_to_function('button_start_from_shortcut', "clicked",
self.on_button_start_from_shortcut_clicked)
self.connect_button_to_function('button_run_to_shortcut', "clicked",
@@ -277,6 +296,9 @@ def register_view(self, view):
"clicked",
self.on_button_step_backward_shortcut_clicked)
+ view['state_machine_search'].connect("search_changed", self.state_machine_search_changed)
+
+
view['upper_notebook'].connect('switch-page', self.on_notebook_tab_switch, view['upper_notebook_title'],
view.left_bar_window, 'upper')
view['lower_notebook'].connect('switch-page', self.on_notebook_tab_switch, view['lower_notebook_title'],
@@ -291,6 +313,7 @@ def register_view(self, view):
view['top_level_h_pane'].connect("button-release-event", self.update_widget_runtime_config, "LEFT_BAR_DOCKED")
view['right_h_pane'].connect("button-release-event", self.update_widget_runtime_config, "RIGHT_BAR_DOCKED")
view['central_v_pane'].connect("button-release-event", self.update_widget_runtime_config, "CONSOLE_DOCKED")
+ view['show_search_bar'].connect("toggled", self.update_search_bar_visibility, "TOGGLED")
# hide not usable buttons
self.view['step_buttons'].hide()
@@ -578,6 +601,9 @@ def on_button_pause_shortcut_toggled(self, widget, event=None):
if self.view['button_pause_shortcut'].get_active():
self.get_controller('menu_bar_controller').on_pause_activate(None)
+ def on_button_start_this_state_shortcut_clicked(self, widget, event=None):
+ self.get_controller('menu_bar_controller').on_run_selected_state_activate(None)
+
def on_button_start_from_shortcut_clicked(self, widget, event=None):
self.get_controller('menu_bar_controller').on_start_from_selected_state_activate(None)
@@ -600,6 +626,12 @@ def on_button_step_out_shortcut_clicked(self, widget, event=None):
def on_button_step_backward_shortcut_clicked(self, widget, event=None):
self.get_controller('menu_bar_controller').on_backward_step_activate(None)
+ def state_machine_search_changed(self, search):
+ library_controller = self.get_controller('library_controller')
+ library_controller.view.collapse_all()
+ library_controller.filter_value = search.get_text().lower()
+ library_controller.filter.refilter()
+
def on_notebook_tab_switch(self, notebook, page, page_num, title_label, window, notebook_identifier):
"""Triggered whenever a left-bar notebook tab is changed.
@@ -625,6 +657,10 @@ def on_switch_page_check_collapse_button(self, notebook, page_num):
self.view["collapse_tree_button"].show()
else:
self.view["collapse_tree_button"].hide()
+ if upper_notebook_title == 'LIBRARIES':
+ self.view["show_search_bar"].show()
+ else:
+ self.view["show_search_bar"].hide()
def on_collapse_button_clicked(self, button):
upper_page_num = self.view['upper_notebook'].get_current_page()
diff --git a/source/rafcon/gui/controllers/menu_bar.py b/source/rafcon/gui/controllers/menu_bar.py
index 188301b2c..fd04753e9 100644
--- a/source/rafcon/gui/controllers/menu_bar.py
+++ b/source/rafcon/gui/controllers/menu_bar.py
@@ -147,6 +147,7 @@ def register_view(self, view):
self.connect_button_to_function('save_state_as', 'activate', self.on_save_selected_state_as_activate)
self.connect_button_to_function('undo', 'activate', self.on_undo_activate)
self.connect_button_to_function('redo', 'activate', self.on_redo_activate)
+ self.connect_button_to_function('search', 'activate', self.on_search_activate)
self.connect_button_to_function('grid', 'activate', self.on_grid_toggled)
self.connect_button_to_function('data_flow_mode', 'toggled', self.on_data_flow_mode_toggled)
@@ -159,6 +160,7 @@ def register_view(self, view):
self.connect_button_to_function('start', 'activate', self.on_start_activate)
self.connect_button_to_function('start_from_selected', 'activate', self.on_start_from_selected_state_activate)
self.connect_button_to_function('run_to_selected', 'activate', self.on_run_to_selected_state_activate)
+ self.connect_button_to_function('run_selected', 'activate', self.on_run_selected_state_activate)
self.connect_button_to_function('pause', 'activate', self.on_pause_activate)
self.connect_button_to_function('stop', 'activate', self.on_stop_activate)
self.connect_button_to_function('step_mode', 'activate', self.on_step_mode_activate)
@@ -352,6 +354,8 @@ def register_actions(self, shortcut_manager):
"on_start_from_selected_state_activate"))
self.add_callback_to_shortcut_manager('run_to_selected', partial(self.call_action_callback,
"on_run_to_selected_state_activate"))
+ self.add_callback_to_shortcut_manager('run_selected', partial(self.call_action_callback,
+ "on_run_selected_state_activate"))
self.add_callback_to_shortcut_manager('stop', partial(self.call_action_callback, "on_stop_activate"))
self.add_callback_to_shortcut_manager('pause', partial(self.call_action_callback, "on_pause_activate"))
@@ -369,6 +373,8 @@ def register_actions(self, shortcut_manager):
self.add_callback_to_shortcut_manager('fullscreen', self.on_toggle_full_screen_mode)
+ self.add_callback_to_shortcut_manager('search', self.on_search_activate)
+
def call_action_callback(self, callback_name, *args, **kwargs):
"""Wrapper for action callbacks
@@ -588,6 +594,11 @@ def on_undo_activate(self, widget, data=None):
def on_redo_activate(self, widget, data=None):
self.shortcut_manager.trigger_action("redo", None, None)
+ def on_search_activate(self, widget, data=None, cursor_position=None):
+ show_search_bar = self.main_window_view["show_search_bar"]
+ show_search_bar.set_active(not show_search_bar.get_active())
+ return True
+
def on_grid_toggled(self, widget, data=None):
pass
@@ -665,6 +676,15 @@ def on_start_from_selected_state_activate(self, widget, data=None):
self.state_machine_execution_engine.start(self.model.selected_state_machine_id,
selection.get_selected_state().state.get_path())
+ def on_run_selected_state_activate(self, widget, data=None):
+ logger.debug("Run selected state ...")
+ selection = gui_singletons.state_machine_manager_model.get_selected_state_machine_model().selection
+ if len(selection.states) is not 1:
+ logger.error("Exactly one state must be selected!")
+ else:
+ self.state_machine_execution_engine.run_selected_state(selection.get_selected_state().state.get_path(),
+ self.model.selected_state_machine_id)
+
def on_pause_activate(self, widget, data=None):
self.state_machine_execution_engine.pause()
diff --git a/source/rafcon/gui/controllers/modification_history.py b/source/rafcon/gui/controllers/modification_history.py
index 5733956dc..87bfbea64 100755
--- a/source/rafcon/gui/controllers/modification_history.py
+++ b/source/rafcon/gui/controllers/modification_history.py
@@ -16,6 +16,7 @@
"""
.. module:: modification_history
:synopsis: A module that holds the controller to list and access the modification-history.
+ :noindex:
"""
diff --git a/source/rafcon/gui/controllers/notification_bar.py b/source/rafcon/gui/controllers/notification_bar.py
index dd7d30e1f..8b30477f6 100644
--- a/source/rafcon/gui/controllers/notification_bar.py
+++ b/source/rafcon/gui/controllers/notification_bar.py
@@ -8,6 +8,8 @@
# Contributors:
# Franz Steinmetz
+import time
+
from gi.repository import Gtk
from rafcon.gui.controllers.utils.extended_controller import ExtendedController
@@ -21,6 +23,7 @@ class NotificationBarController(ExtendedController):
def __init__(self, model, view):
super(NotificationBarController, self).__init__(model, view)
log_helpers.LoggingViewHandler.add_logging_view(self.__class__.__name__, self)
+ self.last_notification_timestamp = None
def register_view(self, view):
super(NotificationBarController, self).register_view(view)
@@ -39,7 +42,10 @@ def print_message(self, message, log_level):
if not self._handle_log_level(log_level):
return
message_text = ": ".join(message.split(": ")[2:]) # remove time, log level and source
- self.view.show_notification(message_text, log_level)
+ now = time.time() * 1000
+ if self.last_notification_timestamp is None or self.last_notification_timestamp + 100 < now:
+ self.view.show_notification(message_text, log_level)
+ self.last_notification_timestamp = now
def _handle_log_level(self, log_level):
minimum_low_level = self.model.config.get_config_value("NOTIFICATIONS_MINIMUM_LOG_LEVEL", 30)
@@ -49,4 +55,4 @@ def _handle_response(self, widget, response_id):
if response_id == Gtk.ResponseType.CLOSE:
if self.view.timer:
self.view.timer.cancel()
- self.view.hide_bar()
+ self.view.hide_bar()
\ No newline at end of file
diff --git a/source/rafcon/gui/controllers/right_click_menu/state.py b/source/rafcon/gui/controllers/right_click_menu/state.py
index 34bb9d871..586813145 100644
--- a/source/rafcon/gui/controllers/right_click_menu/state.py
+++ b/source/rafcon/gui/controllers/right_click_menu/state.py
@@ -225,6 +225,10 @@ def insert_execution_sub_menu_in_menu(self, menu, shortcuts_dict, accel_group):
self.on_run_to_selected_state_activate,
accel_code=shortcuts_dict['run_to_selected'][0],
accel_group=accel_group))
+ execution_sub_menu.append(create_menu_item("run this state", constants.BUTTON_RUN_SELECTED_STATE,
+ self.on_run_selected_state_activate,
+ accel_code=shortcuts_dict['run_selected'][0],
+ accel_group=accel_group))
def insert_copy_cut_paste_in_menu(self, menu, shortcuts_dict, accel_group, no_paste=False):
menu.append(create_menu_item("Copy selection", constants.BUTTON_COPY, self.on_copy_activate,
@@ -280,6 +284,8 @@ def on_select_library_tree_element(widget, date=None, state_m=None):
from rafcon.gui.singleton import main_window_controller
library_tree_controller = main_window_controller.get_controller('library_controller')
library_tree_controller.select_library_tree_element_of_library_state_model(state_m)
+ library_usages_tree_controller = main_window_controller.get_controller('library_usages_controller')
+ library__usagestree_controller.select_library_tree_element_of_library_state_model(state_m)
def on_toggle_is_start_state(self, widget, data=None):
self.shortcut_manager.trigger_action("is_start_state", None, None)
@@ -335,6 +341,9 @@ def on_run_from_selected_state_activate(self, widget, data=None):
def on_run_to_selected_state_activate(self, widget, data=None):
self.shortcut_manager.trigger_action('run_to_selected', None, None)
+ def on_run_selected_state_activate(self, widget, data=None):
+ self.shortcut_manager.trigger_action('run_selected', None, None)
+
@staticmethod
def on_save_as_activate(widget, data=None, path=None, save_as_function=None):
# workaround to set the initial path in the 'choose folder' dialog to the handed one
@@ -420,36 +429,6 @@ def activate_menu(self, event, menu):
return True
-class StateRightClickMenuControllerOpenGLEditor(StateMachineRightClickMenuController):
-
- def register_view(self, view):
- ExtendedController.register_view(self, view)
- from rafcon.gui.views.graphical_editor import GraphicalEditorView
- assert isinstance(view, GraphicalEditorView)
- view.editor.connect('button_press_event', self.mouse_click)
-
- def activate_menu(self, event, menu):
- # logger.info("activate_menu by " + self.__class__.__name__)
- selection = gui_singletons.state_machine_manager_model.get_selected_state_machine_model().selection
- if len(selection.states) > 0 or len(selection.scoped_variables) > 0:
- menu.popup(None, None, None, None, event.get_button()[1], event.time)
- return True
- else:
- return False
-
- def on_copy_activate(self, widget, data=None):
- # logger.info("trigger opengl copy")
- self.shortcut_manager.trigger_action("copy", None, None)
-
- def on_paste_activate(self, widget, data=None):
- # logger.info("trigger opengl paste")
- self.shortcut_manager.trigger_action("paste", None, None)
-
- def on_cut_activate(self, widget, data=None):
- # logger.info("trigger opengl cut")
- self.shortcut_manager.trigger_action("cut", None, None)
-
-
class StateRightClickMenuGaphas(StateMachineRightClickMenu):
""" A class for handling the right click menu inside gaphas
diff --git a/source/rafcon/gui/controllers/state_editor/data_flows.py b/source/rafcon/gui/controllers/state_editor/data_flows.py
index 690e2bd98..2a41fcff8 100644
--- a/source/rafcon/gui/controllers/state_editor/data_flows.py
+++ b/source/rafcon/gui/controllers/state_editor/data_flows.py
@@ -549,7 +549,7 @@ def take_from_dict(from_dict, key):
from_key_port.name
to_key_port = to_state.get_data_port_by_id(data_flow.to_key)
- # to_key_label = ''
+ to_key_label = ''
if to_key_port is not None:
to_key_label = PORT_TYPE_TAG.get(type(to_key_port), 'None') + '.' + \
(to_key_port.data_type.__name__ or 'None') + '.' + \
diff --git a/source/rafcon/gui/controllers/state_editor/semantic_data_editor.py b/source/rafcon/gui/controllers/state_editor/semantic_data_editor.py
index f4ce10842..e0701eb45 100644
--- a/source/rafcon/gui/controllers/state_editor/semantic_data_editor.py
+++ b/source/rafcon/gui/controllers/state_editor/semantic_data_editor.py
@@ -197,7 +197,7 @@ def on_remove(self, widget, data=None):
try:
self.select_entry(self.tree_store[path][self.ID_STORAGE_ID])
except IndexError:
- if len(self.tree_store):
+ if self.tree_store:
if len(path) > 1:
possible_before_path = tuple(list(path[:-1]) + [path[-1] - 1])
if possible_before_path[-1] > -1:
diff --git a/source/rafcon/gui/controllers/state_machines_editor.py b/source/rafcon/gui/controllers/state_machines_editor.py
index f69c410e6..286c52477 100644
--- a/source/rafcon/gui/controllers/state_machines_editor.py
+++ b/source/rafcon/gui/controllers/state_machines_editor.py
@@ -173,7 +173,7 @@ def on_switch_page(self, notebook, page_pointer, page_num):
if self.model.selected_state_machine_id != new_sm_id:
self.model.selected_state_machine_id = new_sm_id
if self.last_focused_state_machine_ids and \
- self.last_focused_state_machine_ids[len(self.last_focused_state_machine_ids) - 1] != new_sm_id:
+ self.last_focused_state_machine_ids[-1] != new_sm_id:
self.last_focused_state_machine_ids.append(new_sm_id)
return page
@@ -335,6 +335,9 @@ def on_mouse_right_click(self, event, state_machine_m, result):
callback=self.on_close_clicked,
callback_args=[state_machine_m, None])
menu.append(menu_item)
+ menu_item = create_menu_item("Close All State Machines", constants.BUTTON_CLOSE,
+ callback=self.on_close_all_clicked)
+ menu.append(menu_item)
menu.show_all()
menu.popup(None, None, None, None, event.get_button()[1], event.time)
@@ -413,6 +416,9 @@ def push_sm_dirty_dialog():
remove_state_machine_m()
return True
+ def on_close_all_clicked(self, event):
+ self.close_all_pages(force=False)
+
def remove_state_machine_tab(self, state_machine_m):
"""
@@ -447,11 +453,11 @@ def remove_state_machine_tab(self, state_machine_m):
else:
self.model.selected_state_machine_id = None
- def close_all_pages(self):
+ def close_all_pages(self, force=True):
"""Closes all tabs of the state machines editor."""
state_machine_m_list = [tab['state_machine_m'] for tab in self.tabs.values()]
for state_machine_m in state_machine_m_list:
- self.on_close_clicked(None, state_machine_m, None, force=True)
+ self.on_close_clicked(None, state_machine_m, None, force)
def refresh_state_machines(self, state_machine_ids):
""" Refresh list af state machine tabs
diff --git a/source/rafcon/gui/controllers/utils/extended_controller.py b/source/rafcon/gui/controllers/utils/extended_controller.py
index 56c88e6b7..cd16c2a6f 100644
--- a/source/rafcon/gui/controllers/utils/extended_controller.py
+++ b/source/rafcon/gui/controllers/utils/extended_controller.py
@@ -175,7 +175,7 @@ def __register_actions_of_child_controllers(self):
assert isinstance(self.__child_controllers, dict)
for controller in self.__child_controllers.values():
register_function = getattr(controller, "register_actions", None)
- if hasattr(register_function, '__call__'):
+ if callable(register_function):
register_function(self.__shortcut_manager)
def connect_signal(self, widget, signal, callback):
@@ -195,7 +195,7 @@ def destroy(self):
all registered models are relieved and and the widget hand by the initial view argument is destroyed.
"""
self.disconnect_all_signals()
- controller_names = [key for key in self.__child_controllers]
+ controller_names = list(self.__child_controllers)
for controller_name in controller_names:
self.remove_controller(controller_name)
self.relieve_all_models()
diff --git a/source/rafcon/gui/glade/main_window.glade b/source/rafcon/gui/glade/main_window.glade
index 0281b4431..3e8ae14fd 100644
--- a/source/rafcon/gui/glade/main_window.glade
+++ b/source/rafcon/gui/glade/main_window.glade
@@ -1,5 +1,5 @@
-
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ True
+
+
+
+
+
+ True
+ True
+ 2
+
@@ -208,11 +264,35 @@
False
+
+
+ True
+ True
+
+
+
+
+
+ 3
+
+
+
+
+ True
+ False
+ Library Usages
+ 90
+
+
+ 3
+ False
+
+
True
True
- 1
+ 2
@@ -496,6 +576,18 @@
True
False
False
+
+
+ True
+ False
+ run_this_state
+ True
+
+
+ False
+ True
+
+
True
@@ -830,17 +922,5 @@
-
-
-
diff --git a/source/rafcon/gui/glade/menu_bar.glade b/source/rafcon/gui/glade/menu_bar.glade
index db09a2384..919fd5d24 100644
--- a/source/rafcon/gui/glade/menu_bar.glade
+++ b/source/rafcon/gui/glade/menu_bar.glade
@@ -1,5 +1,5 @@
-
+
@@ -251,6 +251,14 @@
True
+
+
+
@@ -358,6 +366,14 @@
True
+
+
+