[scram] 01/02: Imported Upstream version 0.14.0
Olzhas Rakhimov
rakhimov-guest at moszumanska.debian.org
Tue Aug 1 02:36:00 UTC 2017
This is an automated email from the git hooks/post-receive script.
rakhimov-guest pushed a commit to branch master
in repository scram.
commit 489466436566bc134f9d8a9906c3961127ddc6c8
Author: rakhimov <ol.rakhimov at gmail.com>
Date: Mon Jul 31 19:33:21 2017 -0700
Imported Upstream version 0.14.0
---
.appveyor.yml | 22 +-
.gitmodules | 3 +
.travis.yml | 25 +-
CMakeLists.txt | 41 +-
CONTRIBUTING.md | 7 +
README.rst | 25 +-
doc/coding_standards.rst | 13 +-
doc/design_description.rst | 6 +
doc/gui.rst | 9 +-
doc/installation.rst | 4 +-
doc/opsa_support.rst | 2 +-
doc/references.rst | 16 +-
doc/release/v0.14.0.md | 42 +
doc/report_file.rst | 2 +-
gui/.clang-format | 1 +
gui/CMakeLists.txt | 106 +-
gui/diagram.cpp | 419 +++
gui/diagram.h | 219 ++
gui/elementcontainermodel.cpp | 362 +++
gui/elementcontainermodel.h | 181 ++
gui/event.cpp | 95 -
gui/event.h | 181 --
gui/eventdialog.cpp | 510 ++++
gui/eventdialog.h | 157 ++
gui/eventdialog.ui | 549 ++++
gui/guiassert.h | 44 +
gui/images/scram128x128.png | Bin 0 -> 21255 bytes
gui/images/scram16x16.png | Bin 0 -> 1127 bytes
gui/images/scram24x24.png | Bin 0 -> 1129 bytes
gui/images/scram256x256.png | Bin 0 -> 59992 bytes
gui/images/scram32x32.png | Bin 0 -> 1892 bytes
gui/images/scram48x48.png | Bin 0 -> 4287 bytes
gui/images/scram64x64.png | Bin 0 -> 7142 bytes
gui/images/scram_logo.svg | 3991 ++++++++++++++++++++++++++++
gui/images/scram_solid.svg | 202 ++
gui/images/scram_solid128x128.png | Bin 0 -> 2043 bytes
gui/images/scram_solid16x16.png | Bin 0 -> 515 bytes
gui/images/scram_solid24x24.png | Bin 0 -> 704 bytes
gui/images/scram_solid256x256.png | Bin 0 -> 3302 bytes
gui/images/scram_solid32x32.png | Bin 0 -> 821 bytes
gui/images/scram_solid48x48.png | Bin 0 -> 1067 bytes
gui/images/scram_solid64x64.png | Bin 0 -> 1332 bytes
gui/main.cpp | 89 +-
gui/mainwindow.cpp | 1374 +++++++++-
gui/mainwindow.h | 169 +-
gui/mainwindow.ui | 801 +++++-
gui/model.cpp | 230 ++
gui/model.h | 622 +++++
gui/modeltree.cpp | 116 +
gui/modeltree.h | 73 +
gui/namedialog.ui | 78 +
gui/{gate.h => overload.h} | 27 +-
gui/{mainwindow.h => printable.cpp} | 49 +-
gui/{gate.cpp => printable.h} | 27 +-
gui/res.qrc | 2 +-
gui/scram-gui.desktop | 9 +
gui/scram.ico | Bin 0 -> 109166 bytes
gui/scram.rc | 1 +
gui/scramgui.cpp | 240 ++
gui/settingsdialog.cpp | 140 +
gui/{mainwindow.h => settingsdialog.h} | 38 +-
gui/settingsdialog.ui | 215 ++
gui/startpage.ui | 76 +
gui/zoomableview.cpp | 70 +
gui/zoomableview.h | 61 +
input/TwoTrain/two_train.xml | 2 +-
share/gui.rng | 142 +-
src/CMakeLists.txt | 20 +-
src/ccf_group.cc | 8 +-
src/ccf_group.h | 8 +-
src/element.cc | 57 +-
src/element.h | 70 +-
src/error.h | 5 +
src/event.cc | 8 +
src/event.h | 59 +-
src/event_tree.cc | 4 +-
src/event_tree.h | 6 +-
src/ext/bits.h | 70 +
src/ext/find_iterator.h | 13 +-
src/ext/multi_index.h | 60 +
src/fault_tree.cc | 49 +-
src/fault_tree.h | 50 +-
src/importance_analysis.cc | 4 +-
src/initializer.cc | 215 +-
src/initializer.h | 73 +-
src/model.cc | 137 +-
src/model.h | 153 +-
src/parameter.h | 2 +-
src/reporter.cc | 2 +-
src/risk_analysis.cc | 8 +-
src/risk_analysis.h | 11 +-
src/scram.cc | 22 +-
src/serialization.cc | 196 ++
src/serialization.h | 52 +
tests/CMakeLists.txt | 1 +
tests/bench_baobab1_tests.cc | 18 +
tests/ccf_group_tests.cc | 18 +-
tests/element_tests.cc | 67 +-
tests/event_tests.cc | 38 +
tests/fault_tree_tests.cc | 50 +-
tests/initializer_tests.cc | 1 +
tests/input/fta/private_at_model_scope.xml | 6 +
tests/performance_tests.h | 5 +-
tests/risk_analysis_tests.cc | 16 +-
tests/serialization_tests.cc | 60 +
105 files changed, 12447 insertions(+), 1080 deletions(-)
diff --git a/.appveyor.yml b/.appveyor.yml
index 30c2047..8bd20c3 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -12,21 +12,37 @@ install:
- git submodule update --init --recursive
- set PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin;%PATH%
- pacman --noconfirm -Syu
- - pacman --noconfirm -Syu
+ - pacman --noconfirm -S mingw-w64-x86_64-cmake
+ - pacman --noconfirm -S mingw-w64-x86_64-extra-cmake-modules
- pacman --noconfirm -S mingw-w64-x86_64-jemalloc
- pacman --noconfirm -S mingw-w64-x86_64-boost
- pacman --noconfirm -S mingw-w64-x86_64-libxml++2.6
- - pacman --noconfirm -S mingw-w64-x86_64-qt5
+ - pacman --noconfirm -S mingw-w64-x86_64-jasper
+ - pacman --noconfirm -S mingw-w64-x86_64-qt5-static
before_build:
- md install
- md build
build_script:
- - python .\install.py --mingw64 --prefix=.\install --build-type=%CONFIGURATION% -j 2
+ - cd build
+ - cmake .. -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=../install -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DCMAKE_CXX_FLAGS="-O3" -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_PREFIX_PATH="C:/msys64/mingw64/qt5-static" -DBUILD_SHARED_LIBS=ON
+ - make install/strip -j 2
+ - cmake .. -DPACKAGE=ON
+ - cpack --verbose
+ - cd ..
after_build:
- set PATH=%PATH%;%CD%\install\bin
test_script:
- scram_tests
+ - scram --version
+ - scram input\TwoTrain\two_train.xml
+
+on_failure:
+ - type build\_CPack_Packages\win64\NSIS\NSISOutput.log
+
+artifacts:
+ - path: build/SCRAM*.exe
+ name: installer
diff --git a/.gitmodules b/.gitmodules
index 8933f82..2f8845f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "scripts/translators"]
path = scripts/translators
url = https://github.com/rakhimov/translators
+[submodule "gui/qtango"]
+ path = gui/qtango
+ url = https://github.com/rakhimov/qtango
diff --git a/.travis.yml b/.travis.yml
index 1207554..c21d968 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,9 +8,7 @@ cache:
directories:
- $HOME/.cache/pip
-os:
- - linux
- - osx
+os: linux
compiler:
- gcc
@@ -27,17 +25,15 @@ env:
- RELEASE=true
matrix:
- exclude:
- - os: osx
- compiler: gcc # GCC is Clang on OS X.
- - os: osx
- env: RELEASE= # OS X builds are slow and limited.
include:
+ - os: osx
+ compiler: clang
+ env: RELEASE=true # OS X builds are slow and limited.
- os: linux
compiler: icc
- env: INTEL_COMPILER=true
+ env: INTEL_COMPILER=true RELEASE=true
allow_failures:
- - compiler: icc # TODO: Boost problems on Travis-CI Trusty
+ - compiler: icc # TODO: C++14 compiler front-end bug
addons:
apt:
@@ -48,11 +44,20 @@ addons:
- libglibmm-2.4-dev
- libgoogle-perftools-dev
- qt5-default
+ - libqt5svg5-dev
+ - libqt5opengl5-dev
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty
- sourceline: "ppa:kzemek/boost"
+before_install:
+ - >
+ echo "$TRAVIS_COMMIT_MESSAGE"
+ | grep -E '\[(skip travis|travis skip)\]'
+ && echo "[skip travis] has been found, exiting."
+ && exit 0 || echo "continue"
+
install:
- .travis/install.sh # Dependencies are built with gcc or clang.
- if [[ -n "${INTEL_COMPILER}" ]]; then
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dabb710..dfdb7a4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,14 +13,14 @@ endif()
project(SCRAM)
set(SCRAM_VERSION_MAJOR 0) # Indicate major API change after 1.0.0
-set(SCRAM_VERSION_MINOR 13) # New features and improvements.
+set(SCRAM_VERSION_MINOR 14) # New features and improvements.
set(SCRAM_VERSION_MICRO 0) # Bug fixes.
####################### Begin Options ###################
option(BUILD_SHARED_LIBS OFF)
-option(INSTALL_LIBS "Install the generated libraries" ON)
+option(INSTALL_LIBS "Install the generated libraries" ${BUILD_SHARED_LIBS})
# Linking of external libraries, such as BOOST.
option(WITH_STATIC_LIBS "Try to link against static libraries" OFF)
@@ -31,6 +31,8 @@ option(WITH_JEMALLOC "Use JEMalloc if available (#2 preference)" ON)
option(WITH_COVERAGE "Instrument for coverage analysis" OFF)
option(WITH_PROFILE "Instrument for performance profiling" OFF)
+option(HAS_TANGO "The system provides the fall-back Tango icons" OFF)
+
option(BUILD_GUI "Build the GUI front-end" ON)
option(BUILD_TESTS "Build the tests" ON)
@@ -77,7 +79,7 @@ elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
CHECK_COMPILER_VERSION("6.1")
-elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")
+elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")
CHECK_COMPILER_VERSION("17.0.1")
# TODO: CMAKE_CXX_STANDARD has no effect on icc.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
@@ -118,10 +120,10 @@ if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${SCRAM_BINARY_DIR}/bin")
endif()
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
- set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${SCRAM_BINARY_DIR}/lib")
+ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${SCRAM_BINARY_DIR}/lib/scram")
endif()
if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
- set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${SCRAM_BINARY_DIR}/lib")
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${SCRAM_BINARY_DIR}/lib/scram")
endif()
set(SCRAM_EXECUTABLE_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
@@ -135,7 +137,7 @@ set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
if(INSTALL_LIBS)
- set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
+ set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib/scram")
endif()
# Add the automatically determined parts of the RPATH,
@@ -210,19 +212,6 @@ message(STATUS " Boost Filesystem location: ${Boost_FILESYSTEM_LIBRARY}")
set(LIBS ${LIBS} ${Boost_DATE_TIME_LIBRARY})
message(STATUS " Boost Date-Time location: ${Boost_DATE_TIME_LIBRARY}")
-if(BUILD_GUI)
- # Find the QtWidgets library.
- if(APPLE)
- # Qt5 with Homebrew.
- set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "/usr/local/opt/qt5")
- endif()
- find_package(Qt5Widgets REQUIRED)
- if(Qt5Widgets_FOUND)
- message(STATUS "Found Qt5")
- message(STATUS "Qt5 Version: ${Qt5Widgets_VERSION_STRING}")
- endif()
-endif()
-
########################## End of find libraries ########################
########################## Begin includes ###############################
@@ -268,7 +257,18 @@ add_custom_target(uninstall
if(PACKAGE)
if(WIN32)
set(CPACK_GENERATOR "NSIS")
+ # set(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}\\\\gui\\\\images\\\\scram_logo.bmp")
+ set(CPACK_NSIS_INSTALLED_ICON_NAME "bin\\\\scram-gui.exe")
+ set(CPACK_NSIS_MODIFY_PATH ON)
+ set(CPACK_NSIS_HELP_LINK "https://scram-pra.org")
+ set(CPACK_NSIS_URL_INFO_ABOUT "https://scram-pra.org")
+ set(CPACK_NSIS_CONTACT "scram-users at googlegroups.com")
+ set(CPACK_PACKAGE_EXECUTABLES "scram-gui" "SCRAM")
+ set(CPACK_CREATE_DESKTOP_LINKS "scram-gui")
+ set(CPACK_COMPONENT_GUI_REQUIRED ON)
# The following configuration assumes MinGW64 is setup specifically for this project.
+ # Moreover, the Qt5 libs and plugins must be linked staticly,
+ # so there's no headache with implicit (undiscoverable) dependencies.
get_filename_component(MINGW_DLL_DIR "${CMAKE_CXX_COMPILER}" PATH)
message(STATUS "MinGW bin location: ${MINGW_DLL_DIR}")
include(GetPrerequisites)
@@ -288,7 +288,6 @@ if(PACKAGE)
${SCRAM_PREREQUISITES}
DESTINATION bin COMPONENT libraries
)
- # TODO: Include GUI dependencies conditionally.
endif()
endif()
@@ -299,6 +298,8 @@ set(CPACK_PACKAGE_VERSION_MINOR "${SCRAM_VERSION_MINOR}")
set(CPACK_PACKAGE_VERSION_PATCH "${SCRAM_VERSION_MICRO}")
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
+set(CPACK_STRIP_FILES ON)
+
set(CPACK_COMPONENTS_ALL scram libraries gui examples)
set(CPACK_COMPONENT_SCRAM_REQUIRED ON)
set(CPACK_COMPONENT_LIBRARIES_REQUIRED ON)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ac7f181..78dba8d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -18,6 +18,13 @@ such as the platform, version number, logs, and (sufficient) input.
[user mailing list]: https://groups.google.com/forum/#!forum/scram-users
+## GUI Issues and Commits
+
+Prepend GUI issue and commit titles with "GUI:".
+For example, a GUI issue can be opened as "GUI: Crash with internal exception",
+and a git commit related to the GUI code can be "GUI: Fix the memory leak in Gate".
+
+
## Developer Workflow
1. Start by forking this repository and setting it as the upstream repository.
diff --git a/README.rst b/README.rst
index bd903fc..0dc62a4 100644
--- a/README.rst
+++ b/README.rst
@@ -51,7 +51,6 @@ The latest stable release is packaged for `quick installation`_ on various platf
.. _quick installation: https://scram-pra.org/doc/installation.html
.. contents:: **Table of Contents**
- :depth: 2
***********************
@@ -139,6 +138,8 @@ The minimal list of required library package names is:
#. libboost-all-dev
#. libxml++2.6-dev
#. qt5-default
+#. libqt5svg5-dev
+#. libqt5opengl5-dev
and (optionally):
@@ -160,7 +161,7 @@ the following line will install all major dependencies:
.. code-block:: bash
- sudo apt-get install -y cmake lib{boost-all,xml++2.6,google-perftools}-dev qt5-default
+ sudo apt-get install -y cmake lib{boost-all,xml++2.6,google-perftools,qt5{svg,opengl}5}-dev qt5-default
macOS
@@ -341,6 +342,26 @@ can be found in the ``scripts`` directory.
Help prompts and the documentation have more details how to use these tools.
+To launch the GUI
+=================
+
+To launch the GUI front-end from the command-line:
+
+.. code-block:: bash
+
+ scram-gui
+
+The command can also take project configuration and/or input files:
+
+.. code-block:: bash
+
+ scram-gui path/to/input/files
+
+ scram-gui --config-file path/to/config/file
+
+ scram-gui path/to/input/files --config-file path/to/config/file
+
+
To run tests
============
diff --git a/doc/coding_standards.rst b/doc/coding_standards.rst
index 862e48e..bb42fb4 100644
--- a/doc/coding_standards.rst
+++ b/doc/coding_standards.rst
@@ -234,9 +234,11 @@ GUI Code
- Upon using Qt containers and constructs,
stick to their STL API and usage style
as much as possible.
- Avoid the Java-style API as much as possible.
+ Avoid the Java-style API.
-- Avoid overloading signals and slots.
+- Upon using Qt specialized containers (e.g., ``QStringList``),
+ do not use a single-element constructor (e.g., ``QStringList(QString)``).
+ Use the initializer list instead.
- Avoid default arguments in signals and slots.
@@ -246,16 +248,15 @@ GUI Code
- Prefer Qt Designer UI forms over hand-coded GUI.
- * The UI class member must be aggregated as a private pointer member
- and named ``ui`` without ``m_`` prefix (default in Qt Creator).
- * Don't repeat ``includes`` provided by the ``ui`` header.
-
- Common Qt includes may be omitted,
for example, ``QString``, ``QList``, ``QStringList``, and ``QDir``.
- Avoid using forward declaration of Qt library classes.
Just include the needed headers.
+- Avoid ``qobject_cast`` and its flavors.
+ Avoid the RTTI in general.
+
Monitoring Code Quality
=======================
diff --git a/doc/design_description.rst b/doc/design_description.rst
index 1881e6b..bc50e27 100644
--- a/doc/design_description.rst
+++ b/doc/design_description.rst
@@ -33,6 +33,12 @@ Core
After the initialization step,
it is not expected that constructs of the analysis change.
+#. The Model container is the top-most container
+ that owns uniquely (non-shared) every object defined and registered in the model
+ either directly or transitively.
+ The analysis input constructs and containers are alive
+ as long as the parent Model container is alive.
+
#. Risk Analyzer operates on a valid model
with initialized fault, event trees, and other constructs
to provide the requested results.
diff --git a/doc/gui.rst b/doc/gui.rst
index a00ec19..37ebccd 100644
--- a/doc/gui.rst
+++ b/doc/gui.rst
@@ -3,11 +3,6 @@ GUI Front-End
#############
.. note::
- The GUI front-end is not in distribution packages.
- It is under developement.
- The documentation is ahead of the developement.
-
-.. note::
The GUI front-end does not provide access
to all the features implemented in SCRAM.
@@ -15,6 +10,9 @@ The GUI front-end is developed with `Qt5 and Qt Creator`_.
.. _Qt5 and Qt Creator: https://www.qt.io/developers/
+The front-end can be launched from the command-line with ``scram-gui``.
+Run ``scram-gui --help`` to get more information.
+
GUI Concerns
============
@@ -102,7 +100,6 @@ GUI Features
- Convenient, effective view of analysis results
- Context menu
- Smart defaults
-- Revision control
Human Interface Guidelines
diff --git a/doc/installation.rst b/doc/installation.rst
index 96bc699..c4c8a91 100644
--- a/doc/installation.rst
+++ b/doc/installation.rst
@@ -10,7 +10,7 @@ Installation Instructions
Debian
======
-SCRAM is available in Debian’s *unstable* and *testing* repositories.
+SCRAM is available in Debian’s official repositories since Debian Stretch.
.. code-block:: bash
@@ -58,7 +58,7 @@ Installation with homebrew_:
brew install homebrew/science/scram
-.. _homebrew: http://brew.sh/
+.. _homebrew: https://brew.sh/
Windows
diff --git a/doc/opsa_support.rst b/doc/opsa_support.rst
index 87e1f4f..52f9915 100644
--- a/doc/opsa_support.rst
+++ b/doc/opsa_support.rst
@@ -38,7 +38,7 @@ The MEF is designed with the "declarative modeling" criterion
and interactions which the model represents".
This paradigm is followed from the structured programming techniques.
-More information about the initiative and format can be found on http://open-psa.org
+More information about the initiative and format can be found on [OPSA]_.
.. _opsa_support:
diff --git a/doc/references.rst b/doc/references.rst
index c99d82b..0f8c2d6 100644
--- a/doc/references.rst
+++ b/doc/references.rst
@@ -73,15 +73,19 @@ Standards, Guides, Manuals, Handbooks
.. [MEF] `Open PSA Model Exchange Format <https://open-psa.github.io/mef>`_
-.. [NUREG0492] `NRC NUREG-0492 <http://www.nrc.gov/reading-rm/doc-collections/nuregs/staff/sr0492/>`_
+.. [NUREG0492] `NRC NUREG-0492 <https://www.nrc.gov/reading-rm/doc-collections/nuregs/staff/sr0492/>`_
-.. [WASH1400] `NRC NUREG-75/014 (WASH-1400), Appendices III & IV <http://www.nrc.gov/reading-rm/doc-collections/nuregs/staff/sr75-014/appendix-iii-iv/>`_
+.. [WASH1400] `NRC NUREG-75/014 (WASH-1400), Appendices III & IV <https://www.nrc.gov/reading-rm/doc-collections/nuregs/staff/sr75-014/appendix-iii-iv/>`_
-.. [CR2300] `NRC NUREG/CR-2300 (PRA Procedure Guide), Vols 1 & 2 <http://www.nrc.gov/reading-rm/doc-collections/nuregs/contract/cr2300/>`_
+.. [CR2300] `NRC NUREG/CR-2300 (PRA Procedure Guide), Vols 1 & 2 <https://www.nrc.gov/reading-rm/doc-collections/nuregs/contract/cr2300/>`_
-.. [FTACOURSE] `FTA: Concepts and Applications <http://www.hq.nasa.gov/office/codeq/risk/docs/ftacourse.pdf>`_
+.. [FTACOURSE] Bill Vesely,
+ "FTA: Concepts and Applications,"
+ NASA Headquarters, Washington DC, 2002.
-.. [FTHB] `Fault Tree Handbook with Aerospace Applications <http://www.hq.nasa.gov/office/codeq/doctree/fthb.pdf>`_
+.. [FTHB] NASA Office of Safety and Mission Assurance,
+ "Fault tree handbook with aerospace applications,"
+ NASA Headquarters, Washington DC, 2002.
.. [STTFTA] `Software Test Techniques for System Fault-Tree Analysis <http://www.cs.virginia.edu/~jck/publications/safecomp.97.pdf>`_
@@ -89,7 +93,7 @@ Standards, Guides, Manuals, Handbooks
Web Resources
=============
-.. [OPSA] `The Open-PSA Initiative <http://open-psa.org>`_
+.. [OPSA] `The Open-PSA Initiative <https://open-psa.github.io>`_
.. [PRA] `PRA Wiki Page <https://en.wikipedia.org/wiki/Probabilistic_risk_assessment>`_
.. [FTA] `FTA Wiki Page <https://en.wikipedia.org/wiki/Fault_tree_analysis>`_
diff --git a/doc/release/v0.14.0.md b/doc/release/v0.14.0.md
new file mode 100644
index 0000000..e9e4d30
--- /dev/null
+++ b/doc/release/v0.14.0.md
@@ -0,0 +1,42 @@
+# Release Notes v0.14.0 : Initial GUI
+
+This is the first GUI release
+with features for simple fault tree analysis.
+
+
+## Major Changes
+
+- GUI: Fault Tree Visualization (#132)
+- GUI: Fault Tree Construction (#133)
+- GUI: Analysis Configuration (#134)
+- GUI: Fault Tree Analysis (#135)
+- GUI: Probability and Importance analysis (#136)
+
+
+## Minor Changes
+
+- GUI: Print the Diagram/Graph (#200)
+- GUI: Print Preview (#205)
+- GUI: Export the Diagram/Graph to SVG (#201)
+- GUI: Transfer In/Out symbols (#191)
+- GUI: Start Page (#196)
+- GUI: Undo/Redo mechanism (#194)
+
+
+## Bug Fixes
+
+- GUI: Icon/Logo quality and scaling issues (#203)
+- Importance factors are not generated for low product order limit (#206)
+- LogicError: MEF Element w/ private role at model scope (#208)
+
+
+## Since v0.13.0
+
+222 commits resulted in 108 files changed, 12550 insertions(+), 1072 deletions(-)
+
+- Core: 27 files changed, 985 insertions(+), 377 deletions(-)
+- Scripts: No change
+- GUI: 54 files changed, 11169 insertions(+), 450 deletions(-)
+- Tests: 11 files changed, 232 insertions(+), 48 deletions(-)
+- Documentation: 8 files changed, 72 insertions(+), 22 deletions(-)
+- Schemas: 1 file changed, 3 insertions(+), 139 deletions(-)
diff --git a/doc/report_file.rst b/doc/report_file.rst
index 0ccfbcc..35c730a 100644
--- a/doc/report_file.rst
+++ b/doc/report_file.rst
@@ -47,7 +47,7 @@ to filter, group, sort, and do other data manipulations and visualization.
Suggested tools:
- - SCRAM GUI front-end *Not Supported Yet*
+ - SCRAM GUI front-end
- R with `FaultTree.SCRAM <https://rdrr.io/rforge/FaultTree.SCRAM/>`_
- Python with `lxml <http://lxml.de/>`_
- `BaseX <http://basex.org>`_
diff --git a/gui/.clang-format b/gui/.clang-format
index 6f38c89..0c4fdf1 100644
--- a/gui/.clang-format
+++ b/gui/.clang-format
@@ -6,4 +6,5 @@ ColumnLimit: 80
Standard: Cpp11
IndentWidth: 4
BreakBeforeBraces: Linux
+AlwaysBreakTemplateDeclarations: true
# ExperimentalAutoDetectBinPacking: true
diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt
index a8e976b..5f93c72 100644
--- a/gui/CMakeLists.txt
+++ b/gui/CMakeLists.txt
@@ -1,4 +1,15 @@
-set(BUILD_SHARED_LIBS FALSE) # GUI code should never be used by the core.
+# Find the QtWidgets library.
+if(APPLE)
+ # Qt5 with Homebrew.
+ set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "/usr/local/opt/qt5")
+endif()
+find_package(Qt5Widgets REQUIRED)
+find_package(Qt5Svg REQUIRED)
+find_package(Qt5OpenGL REQUIRED)
+find_package(Qt5PrintSupport REQUIRED)
+find_package(Qt5Concurrent REQUIRED)
+message(STATUS "Found Qt5")
+message(STATUS "Qt5 Version: ${Qt5Widgets_VERSION_STRING}")
# Prevent implicit QString(const char*), string concat with "+", and other anti-patterns.
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII -DQT_NO_CAST_FROM_BYTEARRAY -DQT_NO_URL_CAST_FROM_STRING -DQT_USE_QSTRINGBUILDER")
@@ -6,6 +17,10 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DQT_NO_CAST_FROM_ASCII -DQT
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")
# TODO: Qt moc offsetof applied to non-POD types is nonstandard.
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -diag-disable=1875")
+elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+ if(Qt5Widgets_VERSION_STRING VERSION_LESS 5.4)
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wno-inconsistent-missing-override")
+ endif()
endif()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@@ -14,32 +29,105 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
+# TODO: Use the autogeneration for static Qt plugin linking by CMake (buggy as of Qt 5.8).
+# if(WIN32)
+# set(CMAKE_AUTOSTATICPLUGINS ON) # Special mingw64 cmake extension.
+# endif()
+
set(SCRAM_GUI_RES "${CMAKE_CURRENT_SOURCE_DIR}/res.qrc")
+
+if(NOT HAS_TANGO AND (WIN32 OR APPLE))
+ set(TANGO_QRC "${CMAKE_CURRENT_SOURCE_DIR}/qtango/icons/tango/tango.qrc")
+ execute_process(
+ COMMAND python generate_rcc.py icons/tango/index.theme
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/qtango"
+ RESULT_VARIABLE qtango_return
+ OUTPUT_VARIABLE qtango_out
+ ERROR_VARIABLE qtango_out
+ )
+ if(qtango_return)
+ message(FATAL_ERROR "Tango icon generation failed: ${qtango_out}")
+ endif()
+ list(APPEND SCRAM_GUI_RES "${TANGO_QRC}")
+endif()
+
set(SCRAM_GUI_SRC
+ "${CMAKE_CURRENT_SOURCE_DIR}/model.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/modeltree.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/elementcontainermodel.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/settingsdialog.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/eventdialog.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/printable.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/zoomableview.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/diagram.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/mainwindow.cpp"
- "${CMAKE_CURRENT_SOURCE_DIR}/event.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/scramgui.cpp"
)
-set(SCRAM_GUI_BIN "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp")
set(SCRAM_GUI_TS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/translations")
add_subdirectory("${SCRAM_GUI_TS_DIR}")
add_library(scramgui ${SCRAM_GUI_SRC} ${SCRAM_GUI_RES})
+set(GUI_DEP_LIBS # TODO: Link with shared libraries on Windows instead.
+ ${LIBS} scramcore
+ Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Svg Qt5::OpenGL Qt5::PrintSupport Qt5::Concurrent)
-target_link_libraries(scramgui Qt5::Core Qt5::Gui Qt5::Widgets)
-
-target_link_libraries(scramgui scramcore ${LIBS})
+if(INSTALL_LIBS)
+ if(WIN32)
+ target_link_libraries(scramgui PRIVATE ${GUI_DEP_LIBS}) # TODO: Remove.
+ install(TARGETS scramgui RUNTIME DESTINATION bin COMPONENT gui)
+ else()
+ target_link_libraries(scramgui ${GUI_DEP_LIBS}) # TODO: Make default.
+ set_target_properties(scramgui
+ PROPERTIES
+ INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib/scram"
+ INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib/scram"
+ )
+ install(
+ TARGETS scramgui
+ DESTINATION lib/scram
+ COMPONENT gui
+ )
+ endif()
+endif()
-add_executable(scram-gui ${SCRAM_GUI_BIN})
-target_link_libraries(scram-gui scramgui scramcore ${Boost_LIBRARIES})
+if(WIN32)
+ add_executable(scram-gui WIN32
+ "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/scram.rc")
+else()
+ add_executable(scram-gui "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp")
+endif()
+target_link_libraries(scram-gui scramgui)
install(TARGETS scram-gui
RUNTIME DESTINATION bin
COMPONENT gui
)
+if(UNIX AND NOT APPLE)
+ foreach (size IN ITEMS 32 128)
+ install(
+ FILES "${CMAKE_CURRENT_SOURCE_DIR}/images/scram_solid${size}x${size}.png"
+ DESTINATION "share/icons/hicolor/${size}x${size}/apps"
+ COMPONENT gui
+ RENAME "scram.png")
+ endforeach ()
+ install(
+ FILES "${CMAKE_CURRENT_SOURCE_DIR}/images/scram_solid.svg"
+ DESTINATION "share/icons/hicolor/scalable/apps"
+ COMPONENT gui
+ RENAME "scram.svg")
+
+ # Install a desktop file
+ # so that SCRAM appears in the application start menu with an icon.
+ install(FILES scram-gui.desktop
+ DESTINATION "share/applications"
+ COMPONENT gui)
+endif()
+
set(SCRAM_GUI_TEST_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests")
-if(BUILD_TESTS)
+if(BUILD_TESTS AND NOT WIN32)
add_subdirectory("${SCRAM_GUI_TEST_DIR}")
endif()
diff --git a/gui/diagram.cpp b/gui/diagram.cpp
new file mode 100644
index 0000000..5e460ec
--- /dev/null
+++ b/gui/diagram.cpp
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2016-2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "diagram.h"
+
+#include <cmath>
+
+#include <unordered_map>
+#include <vector>
+
+#include <QApplication>
+#include <QFontMetrics>
+#include <QGraphicsLineItem>
+#include <QGraphicsPathItem>
+#include <QGraphicsPolygonItem>
+#include <QGraphicsRectItem>
+#include <QGraphicsTextItem>
+#include <QPainter>
+#include <QPainterPath>
+#include <QRectF>
+#include <QStyleOptionGraphicsItem>
+
+#include "src/ext/find_iterator.h"
+
+#include "guiassert.h"
+#include "overload.h"
+
+namespace scram {
+namespace gui {
+namespace diagram {
+
+const QSize Event::m_size = {16, 11};
+const double Event::m_baseHeight = 6.5;
+const double Event::m_idBoxLength = 10;
+const double Event::m_labelBoxHeight = 4;
+
+Event::Event(model::Element *event, QGraphicsItem *parent)
+ : QGraphicsItem(parent), m_event(event), m_typeGraphics(nullptr)
+{
+ m_labelConnection = QObject::connect(event, &model::Element::labelChanged,
+ [this] { update(); });
+ m_idConnection = QObject::connect(event, &model::Element::idChanged,
+ [this] { update(); });
+ setFlag(QGraphicsItem::ItemIsSelectable, true);
+}
+
+Event::~Event() noexcept
+{
+ QObject::disconnect(m_labelConnection);
+ QObject::disconnect(m_idConnection);
+}
+
+QSize Event::units() const
+{
+ QFontMetrics font = QApplication::fontMetrics();
+ return {font.averageCharWidth(), font.height()};
+}
+
+double Event::width() const
+{
+ return m_size.width() * units().width();
+}
+
+void Event::setTypeGraphics(QGraphicsItem *item)
+{
+ delete m_typeGraphics;
+ m_typeGraphics = item;
+ m_typeGraphics->setParentItem(this);
+ m_typeGraphics->setPos(0, m_baseHeight * units().height());
+}
+
+QRectF Event::boundingRect() const
+{
+ int w = units().width();
+ int h = units().height();
+ double labelBoxWidth = m_size.width() * w;
+ return QRectF(-labelBoxWidth / 2, 0, labelBoxWidth, m_baseHeight * h);
+}
+
+void Event::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+ QWidget * /*widget*/)
+{
+ if (option->state & QStyle::State_Selected)
+ painter->setBrush(QColor("cyan"));
+
+ int w = units().width();
+ int h = units().height();
+ double labelBoxWidth = m_size.width() * w ;
+ QRectF rect(-labelBoxWidth / 2, 0, labelBoxWidth, m_labelBoxHeight * h);
+ painter->drawRect(rect);
+ painter->drawText(rect, Qt::AlignCenter | Qt::TextWordWrap,
+ m_event->label());
+
+ painter->drawLine(QPointF(0, m_labelBoxHeight * h),
+ QPointF(0, (m_labelBoxHeight + 1) * h));
+
+ double idBoxWidth = m_idBoxLength * w;
+ QRectF nameRect(-idBoxWidth / 2, (m_labelBoxHeight + 1) * h, idBoxWidth, h);
+ painter->drawRect(nameRect);
+ painter->drawText(nameRect, Qt::AlignCenter, m_event->id());
+
+ painter->drawLine(QPointF(0, (m_labelBoxHeight + 2) * h),
+ QPointF(0, (m_labelBoxHeight + 2.5) * h));
+}
+
+BasicEvent::BasicEvent(model::BasicEvent *event, QGraphicsItem *parent)
+ : Event(event, parent)
+{
+ double d = int(m_size.height() - m_baseHeight) * units().height();
+ Event::setTypeGraphics(new QGraphicsEllipseItem(-d / 2, 0, d, d));
+}
+
+HouseEvent::HouseEvent(model::HouseEvent *event, QGraphicsItem *parent)
+ : Event(event, parent)
+{
+ double h = int(m_size.height() - m_baseHeight) * units().height();
+ double y0 = h * 0.25;
+ Event::setTypeGraphics(new QGraphicsPolygonItem(
+ {{{0, 0}, {-h / 2, y0}, {-h / 2, h}, {h / 2, h}, {h / 2, y0}}}));
+}
+
+UndevelopedEvent::UndevelopedEvent(model::BasicEvent *event,
+ QGraphicsItem *parent)
+ : Event(event, parent)
+{
+ double h = int(m_size.height() - m_baseHeight) * units().height();
+ double a = h / std::sqrt(2);
+ auto *diamond = new QGraphicsRectItem(-a / 2, (h - a) / 2, a, a);
+ diamond->setTransformOriginPoint(0, h / 2);
+ diamond->setRotation(45);
+ Event::setTypeGraphics(diamond);
+}
+
+ConditionalEvent::ConditionalEvent(model::BasicEvent *event,
+ QGraphicsItem *parent)
+ : Event(event, parent)
+{
+ double d = int(m_size.height() - m_baseHeight) * units().height();
+ double minor = 0.70 * d;
+ Event::setTypeGraphics(new QGraphicsEllipseItem(-d / 2, 0, d, minor));
+}
+
+TransferIn::TransferIn(model::Gate *event, QGraphicsItem *parent)
+ : Event(event, parent)
+{
+ double d = int(m_size.height() - m_baseHeight) * units().height();
+ Event::setTypeGraphics(
+ new QGraphicsPolygonItem({{{0, 0}, {-d / 2, d}, {d / 2, d}}}));
+}
+
+const QSize Gate::m_maxSize = {6, 3};
+const double Gate::m_space = 1;
+
+Gate::Gate(model::Gate *event, model::Model *model,
+ std::unordered_map<const mef::Gate *, Gate *> *transfer,
+ QGraphicsItem *parent)
+ : Event(event, parent)
+{
+ double availableHeight
+ = m_size.height() - m_baseHeight - m_maxSize.height();
+ auto *pathItem = new QGraphicsLineItem(
+ 0, 0, 0, (availableHeight - 1) * units().height(), this);
+ pathItem->setPos(0, (m_baseHeight + m_maxSize.height()) * units().height());
+ Event::setTypeGraphics(getGateGraphicsType(event->type()).release());
+ struct {
+ Event *operator()(const mef::BasicEvent *arg)
+ {
+ model::BasicEvent *proxyEvent
+ = m_model->basicEvents().find(arg)->get();
+ switch (proxyEvent->flavor()) {
+ case model::BasicEvent::Basic:
+ return new BasicEvent(proxyEvent, m_parent);
+ case model::BasicEvent::Undeveloped:
+ return new UndevelopedEvent(proxyEvent, m_parent);
+ case model::BasicEvent::Conditional:
+ return new ConditionalEvent(proxyEvent, m_parent);
+ }
+ GUI_ASSERT(false && "Unexpected event flavor", nullptr);
+ }
+ Event *operator()(const mef::HouseEvent *arg)
+ {
+ return new HouseEvent(m_model->houseEvents().find(arg)->get(),
+ m_parent);
+ }
+ Event *operator()(const mef::Gate *arg)
+ {
+ model::Gate *proxyEvent = m_model->gates().find(arg)->get();
+ if (auto it = ext::find(*m_transfer, arg)) {
+ it->second->addTransferOut();
+ return new TransferIn(proxyEvent, m_parent);
+ }
+ auto *arg_gate
+ = new Gate(proxyEvent, m_model, m_transfer, m_parent);
+ m_transfer->emplace(arg, arg_gate);
+ return arg_gate;
+ }
+
+ QGraphicsItem *m_parent;
+ decltype(model) m_model;
+ decltype(transfer) m_transfer;
+ } formula_visitor{this, model, transfer};
+ double linkY = (m_size.height() - 1) * units().height();
+ std::vector<std::pair<Event *, QGraphicsLineItem *>> children;
+ for (const mef::Formula::EventArg &eventArg : event->args()) {
+ auto *child = boost::apply_visitor(formula_visitor, eventArg);
+ auto *link = new QGraphicsLineItem(0, 0, 0, units().height(), this);
+ if (!children.empty())
+ m_width += m_space * units().height();
+ child->moveBy(m_width + child->width() / 2,
+ m_size.height() * units().height());
+ link->moveBy(m_width + child->width() / 2, linkY);
+ m_width += child->width();
+ children.emplace_back(child, link);
+ }
+ // Shift the children left.
+ for (auto &child : children) {
+ child.first->moveBy(-m_width / 2, 0);
+ child.second->moveBy(-m_width / 2, 0);
+ }
+ // Add the planar line to complete the connection.
+ if (children.size() > 1)
+ new QGraphicsLineItem(children.front().first->pos().x(), linkY,
+ children.back().first->pos().x(), linkY, this);
+}
+
+std::unique_ptr<QGraphicsItem> Gate::getGateGraphicsType(mef::Operator type)
+{
+ static_assert(mef::kNumOperators == 8, "Unexpected operator changes");
+ switch (type) {
+ case mef::kNull:
+ return std::make_unique<QGraphicsLineItem>(
+ 0, 0, 0, m_maxSize.height() * units().height());
+ case mef::kAnd: {
+ double h = m_maxSize.height() * units().height();
+ QPainterPath paintPath;
+ double maxHeight = m_maxSize.height() * units().height();
+ paintPath.moveTo(0, maxHeight);
+ paintPath.arcTo(-h / 2, 0, h, maxHeight * 2, 0, 180);
+ paintPath.closeSubpath();
+ return std::make_unique<QGraphicsPathItem>(paintPath);
+ }
+ case mef::kOr: {
+ QPainterPath paintPath;
+ double x1 = m_maxSize.width() * units().width() / 2;
+ double maxHeight = m_maxSize.height() * units().height();
+ QRectF rectangle(-x1, 0, x1 * 2, maxHeight * 2);
+ paintPath.arcMoveTo(rectangle, 0);
+ paintPath.arcTo(rectangle, 0, 180);
+ double lowerArc = 0.25;
+ rectangle.setHeight(rectangle.height() * lowerArc);
+ rectangle.moveTop(maxHeight * (1 - lowerArc));
+ paintPath.arcMoveTo(rectangle, 0);
+ paintPath.arcTo(rectangle, 0, 180);
+ paintPath.arcMoveTo(rectangle, 90);
+ paintPath.lineTo(0, maxHeight);
+ return std::make_unique<QGraphicsPathItem>(paintPath);
+ }
+ case mef::kVote: {
+ double h = m_maxSize.height() * units().height();
+ double a = h / sqrt(3);
+ auto polygon
+ = std::make_unique<QGraphicsPolygonItem>(QPolygonF{{{-a / 2, 0},
+ {a / 2, 0},
+ {a, h / 2},
+ {a / 2, h},
+ {-a / 2, h},
+ {-a, h / 2}}});
+ auto *gate = static_cast<model::Gate *>(m_event);
+ auto *text = new QGraphicsTextItem(QString::fromLatin1("%1/%2")
+ .arg(gate->voteNumber())
+ .arg(gate->numArgs()),
+ polygon.get());
+ QFont font = text->font();
+ font.setPointSizeF(1.5 * font.pointSizeF());
+ text->setFont(font);
+ text->setPos(-text->boundingRect().width() / 2,
+ (h - text->boundingRect().height()) / 2);
+ return std::move(polygon);
+ }
+ case mef::kNot: {
+ double h = m_maxSize.height() * units().height();
+ QPainterPath paintPath;
+ paintPath.addEllipse(-units().height() / 2, 0, units().height(),
+ units().height());
+ paintPath.moveTo(0, units().height());
+ double a = h - units().height();
+ paintPath.lineTo(-a / 2, h);
+ paintPath.lineTo(a / 2, h);
+ paintPath.closeSubpath();
+ return std::make_unique<QGraphicsPathItem>(paintPath);
+ }
+ case mef::kXor: {
+ auto orItem = getGateGraphicsType(mef::kOr);
+ double x1 = m_maxSize.width() * units().width() / 2.0;
+ double h = m_maxSize.height() * units().height();
+ QPainterPath paintPath;
+ paintPath.lineTo(-x1, h);
+ paintPath.moveTo(x1, h);
+ paintPath.lineTo(0, 0);
+ new QGraphicsPathItem(paintPath, orItem.get());
+ return orItem;
+ }
+ case mef::kNor: {
+ auto orItem = getGateGraphicsType(mef::kOr);
+ auto circle = std::make_unique<QGraphicsEllipseItem>(
+ -units().height() / 2, 0, units().height(), units().height());
+ double orHeight = orItem->boundingRect().height();
+ orItem->setScale((orHeight - units().height()) / orHeight);
+ orItem->setPos(0, units().height());
+ orItem.release()->setParentItem(circle.get());
+ return std::move(circle);
+ }
+ case mef::kNand: {
+ auto andItem = getGateGraphicsType(mef::kAnd);
+ auto circle = std::make_unique<QGraphicsEllipseItem>(
+ -units().height() / 2, 0, units().height(), units().height());
+ double andHeight = andItem->boundingRect().height();
+ andItem->setScale((andHeight - units().height()) / andHeight);
+ andItem->setPos(0, units().height());
+ andItem.release()->setParentItem(circle.get());
+ return std::move(circle);
+ }
+ }
+ GUI_ASSERT(false && "Unexpected gate type", nullptr);
+}
+
+double Gate::width() const { return m_width; }
+
+void Gate::addTransferOut()
+{
+ if (m_transferOut)
+ return;
+ m_transferOut = true;
+ QPainterPath paintPath;
+ double x1 = m_maxSize.width() * units().width() / 2.0;
+ double h = units().height() * std::sqrt(3) / 2;
+ paintPath.lineTo(x1 + units().height(), 0);
+ paintPath.lineTo(x1 + 0.5 * units().height(), h);
+ paintPath.lineTo(x1 + 1.5 * units().height(), h);
+ paintPath.lineTo(x1 + units().height(), 0);
+ new QGraphicsPathItem(paintPath, Event::getTypeGraphics());
+}
+
+DiagramScene::DiagramScene(model::Gate *event, model::Model *model,
+ QObject *parent)
+ : QGraphicsScene(parent), m_root(event), m_model(model)
+{
+ redraw();
+ connect(m_model, OVERLOAD(model::Model, removed, model::Gate *), this,
+ [this](model::Gate *gate) {
+ if (gate == m_root) {
+ clear();
+ m_root = nullptr; ///< @todo Remove the implicit delete.
+ }
+ });
+}
+
+void DiagramScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent)
+{
+ QGraphicsScene::mouseDoubleClickEvent(mouseEvent);
+ const QList<QGraphicsItem *> items = selectedItems();
+ if (!items.empty()) {
+ GUI_ASSERT(items.size() == 1, );
+ auto *event = dynamic_cast<diagram::Event *>(items.front());
+ GUI_ASSERT(event, );
+ emit activated(event->data());
+ }
+}
+
+void DiagramScene::redraw()
+{
+ if (m_root == nullptr)
+ return;
+
+ clear();
+ std::unordered_map<const mef::Gate *, Gate *> transfer;
+ addItem(new Gate(m_root, m_model, &transfer));
+
+ struct {
+ void operator()(mef::Event *) const {}
+ void operator()(mef::BasicEvent *event) const
+ {
+ auto *proxy = self->m_model->basicEvents().find(event)->get();
+ connect(proxy, &model::BasicEvent::flavorChanged, self,
+ &DiagramScene::redraw, Qt::UniqueConnection);
+ }
+ DiagramScene *self;
+ } visitor{this};
+
+ auto link = [this, &visitor](model::Gate *gate) {
+ connect(gate, &model::Gate::formulaChanged, this,
+ &DiagramScene::redraw, Qt::UniqueConnection);
+ for (const mef::Formula::EventArg &arg : gate->args())
+ boost::apply_visitor(visitor, arg);
+ };
+
+ /// @todo Finer signal tracking.
+ link(m_root);
+ for (const auto &entry : transfer)
+ link(m_model->gates().find(entry.first)->get());
+}
+
+} // namespace diagram
+} // namespace gui
+} // namespace scram
diff --git a/gui/diagram.h b/gui/diagram.h
new file mode 100644
index 0000000..189e271
--- /dev/null
+++ b/gui/diagram.h
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2016-2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DIAGRAM_H
+#define DIAGRAM_H
+
+#include <memory>
+#include <unordered_map>
+
+#include <QGraphicsItem>
+#include <QGraphicsScene>
+#include <QSize>
+
+#include "src/event.h"
+
+#include "model.h"
+
+namespace scram {
+namespace gui {
+namespace diagram {
+
+/**
+ * @brief The base class for probabilistic events in a fault tree.
+ *
+ * The base event item provides
+ * only the boxes containing the name and description of the event.
+ * A derived class must provide
+ * the symbolic representation of its kind.
+ *
+ * The sizes are measured in units of character height and average width.
+ * This class provides the reference units for derived classes to use.
+ * All derived class shapes should stay within the allowed box limits
+ * to make the fault tree structure layered.
+ */
+class Event : public QGraphicsItem
+{
+public:
+ ~Event() noexcept override;
+
+ QRectF boundingRect() const final;
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+ QWidget *widget) final;
+
+ /// @returns The width of the whole subgraph.
+ virtual double width() const;
+
+ /// @returns The model data event.
+ model::Element *data() const { return m_event; }
+
+protected:
+ /// The confining size of the Event graphics in characters.
+ /// The derived event types should stay within this confinement.
+ static const QSize m_size;
+ /// The height of the confining space used only by the Event base class.
+ static const double m_baseHeight;
+ /// The length of the ID box in characters.
+ /// The height of the ID box is 1 character.
+ static const double m_idBoxLength;
+ /// The height of the Label box in characters.
+ static const double m_labelBoxHeight;
+
+ /**
+ * @brief Assigns an event to a presentation view.
+ *
+ * @param event The model event.
+ * @param parent The parent Graphics event.
+ */
+ explicit Event(model::Element *event, QGraphicsItem *parent = nullptr);
+
+ /**
+ * @return The graphics of the derived class.
+ */
+ QGraphicsItem *getTypeGraphics() const { return m_typeGraphics; }
+
+ /**
+ * @brief Releases the current derived class item,
+ * and sets the new one.
+ *
+ * @param item The new item to represent the derived type.
+ */
+ void setTypeGraphics(QGraphicsItem *item);
+
+ /**
+ * @return Unit width (x) and height (y) for shapes.
+ */
+ QSize units() const;
+
+ model::Element *m_event; ///< The model data.
+
+private:
+ QGraphicsItem *m_typeGraphics; ///< The graphics of the derived type.
+ QMetaObject::Connection m_labelConnection; ///< Tracks the label changes.
+ QMetaObject::Connection m_idConnection; ///< Tracks the ID changes.
+};
+
+/**
+ * @brief Representation of a fault tree basic event.
+ */
+class BasicEvent : public Event
+{
+public:
+ explicit BasicEvent(model::BasicEvent *event,
+ QGraphicsItem *parent = nullptr);
+};
+
+/**
+ * @brief Representation of a fault tree house events.
+ */
+class HouseEvent : public Event
+{
+public:
+ explicit HouseEvent(model::HouseEvent *event,
+ QGraphicsItem *parent = nullptr);
+};
+
+/**
+ * @brief Placeholder for events with a potential to become a gate.
+ */
+class UndevelopedEvent : public Event
+{
+public:
+ explicit UndevelopedEvent(model::BasicEvent *event,
+ QGraphicsItem *parent = nullptr);
+};
+
+/**
+ * @brief The event used in Inhibit gates.
+ */
+class ConditionalEvent : public Event
+{
+public:
+ explicit ConditionalEvent(model::BasicEvent *event,
+ QGraphicsItem *parent = nullptr);
+};
+
+/**
+ * An alias pointer to a gate.
+ */
+class TransferIn : public Event
+{
+public:
+ explicit TransferIn(model::Gate *event, QGraphicsItem *parent = nullptr);
+};
+
+/**
+ * @brief Fault tree intermediate events or gates.
+ */
+class Gate : public Event
+{
+public:
+ /**
+ * @brief Constructs the graph with the transfer symbols for gates.
+ *
+ * @param event The event to be converted into a graphics item.
+ * @param model The model with wrapper object with signals.
+ * @param transfer The set of already processed gates
+ * to be shown as transfer event.
+ * @param parent The optional parent graphics item.
+ */
+ Gate(model::Gate *event, model::Model *model,
+ std::unordered_map<const mef::Gate *, Gate *> *transfer,
+ QGraphicsItem *parent = nullptr);
+
+ std::unique_ptr<QGraphicsItem> getGateGraphicsType(mef::Operator type);
+
+ double width() const override;
+
+ /**
+ * @brief Adds the transfer-out symbol besides the gate shape.
+ */
+ void addTransferOut();
+
+private:
+ static const QSize m_maxSize; ///< The constraints on type graphics.
+ static const double m_space; ///< The space between children in chars.
+
+ double m_width = 0; ///< Assume the graph does not change its width.
+ bool m_transferOut = false; ///< The indication of the transfer-out.
+};
+
+class DiagramScene : public QGraphicsScene
+{
+ Q_OBJECT
+
+public:
+ DiagramScene(model::Gate *event, model::Model *model,
+ QObject *parent = nullptr);
+signals:
+ void activated(model::Element *event);
+
+private:
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent) override;
+
+ /// Track changes more accurately.
+ void redraw();
+
+ model::Gate *m_root;
+ model::Model *m_model;
+};
+
+} // namespace diagram
+} // namespace gui
+} // namespace scram
+
+#endif // DIAGRAM_H
diff --git a/gui/elementcontainermodel.cpp b/gui/elementcontainermodel.cpp
new file mode 100644
index 0000000..b017980
--- /dev/null
+++ b/gui/elementcontainermodel.cpp
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file elementcontainermodel.cpp
+
+#include "elementcontainermodel.h"
+
+#include "src/ext/variant.h"
+#include "src/event.h"
+#include "src/model.h"
+
+#include "guiassert.h"
+#include "overload.h"
+
+namespace scram {
+namespace gui {
+namespace model {
+
+template <class T>
+ElementContainerModel::ElementContainerModel(const T &container, Model *model,
+ QObject *parent)
+ : QAbstractItemModel(parent)
+{
+ m_elements.reserve(container.size());
+ m_elementToIndex.reserve(container.size());
+ for (const auto &elementPtr : container) {
+ m_elementToIndex.emplace(elementPtr.get(), m_elements.size());
+ m_elements.push_back(elementPtr.get());
+ }
+ using E = typename T::value_type::element_type;
+ connect(model, OVERLOAD(Model, added, E *), this,
+ &ElementContainerModel::addElement);
+ connect(model, OVERLOAD(Model, removed, E *), this,
+ &ElementContainerModel::removeElement);
+}
+
+void ElementContainerModel::connectElement(Element *element)
+{
+ connect(element, &Element::labelChanged, this, [this, element] {
+ QModelIndex index
+ = createIndex(getElementIndex(element), columnCount() - 1, element);
+ emit dataChanged(index, index);
+ });
+ connect(element, &Element::idChanged, this, [this, element] {
+ QModelIndex index = createIndex(getElementIndex(element), 0, element);
+ emit dataChanged(index, index);
+ });
+}
+
+QModelIndex ElementContainerModel::index(int row, int column,
+ const QModelIndex &parent) const
+{
+ GUI_ASSERT(parent.isValid() == false, {});
+ return createIndex(row, column, getElement(row));
+}
+
+Element *ElementContainerModel::getElement(int index) const
+{
+ GUI_ASSERT(index < m_elements.size(), nullptr);
+ return m_elements[index];
+}
+
+int ElementContainerModel::getElementIndex(Element *element) const
+{
+ auto it = m_elementToIndex.find(element);
+ GUI_ASSERT(it != m_elementToIndex.end(), -1);
+ return it->second;
+}
+
+void ElementContainerModel::addElement(Element *element)
+{
+ int index = m_elements.size();
+ beginInsertRows({}, index, index);
+ m_elementToIndex.emplace(element, index);
+ m_elements.push_back(element);
+ endInsertRows();
+ connectElement(element);
+}
+
+void ElementContainerModel::removeElement(Element *element)
+{
+ GUI_ASSERT(m_elementToIndex.count(element), );
+ int index = m_elementToIndex.find(element)->second;
+ int lastIndex = m_elements.size() - 1;
+ auto *lastElement = m_elements.back();
+ // The following is basically a swap with the last item.
+ beginRemoveRows({}, lastIndex, lastIndex);
+ m_elementToIndex.erase(element);
+ m_elements.pop_back();
+ endRemoveRows();
+ if (index != lastIndex) {
+ m_elements[index] = lastElement;
+ m_elementToIndex[lastElement] = index;
+ emit dataChanged(createIndex(index, 0, lastElement),
+ createIndex(index, columnCount() - 1, lastElement));
+ }
+ disconnect(element, 0, this, 0);
+}
+
+int ElementContainerModel::rowCount(const QModelIndex &parent) const
+{
+ return parent.isValid() ? 0 : m_elements.size();
+}
+
+BasicEventContainerModel::BasicEventContainerModel(Model *model,
+ QObject *parent)
+ : ElementContainerModel(model->basicEvents(), model, parent)
+{
+ for (Element *element : elements())
+ connectElement(element);
+}
+
+int BasicEventContainerModel::columnCount(const QModelIndex &parent) const
+{
+ return parent.isValid() ? 0 : 4;
+}
+
+QVariant BasicEventContainerModel::headerData(int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
+ return ElementContainerModel::headerData(section, orientation, role);
+
+ switch (section) {
+ case 0:
+ return tr("ID");
+ case 1:
+ return tr("Flavor");
+ case 2:
+ return tr("Probability");
+ case 3:
+ return tr("Label");
+ }
+ GUI_ASSERT(false && "unexpected header section", {});
+}
+
+QVariant BasicEventContainerModel::data(const QModelIndex &index,
+ int role) const
+{
+ if (!index.isValid() || role != Qt::DisplayRole)
+ return {};
+ auto *basicEvent = static_cast<BasicEvent *>(index.internalPointer());
+
+ switch (index.column()) {
+ case 0:
+ return basicEvent->id();
+ case 1:
+ return BasicEvent::flavorToString(basicEvent->flavor());
+ case 2:
+ return basicEvent->probability<QVariant>();
+ case 3:
+ return basicEvent->label();
+ }
+ GUI_ASSERT(false && "unexpected column", {});
+}
+
+void BasicEventContainerModel::connectElement(Element *element)
+{
+ ElementContainerModel::connectElement(element);
+ connect(static_cast<BasicEvent *>(element), &BasicEvent::flavorChanged,
+ this, [this, element] {
+ QModelIndex index
+ = createIndex(getElementIndex(element), 1, element);
+ emit dataChanged(index, index);
+ });
+ connect(static_cast<BasicEvent *>(element), &BasicEvent::expressionChanged,
+ this, [this, element] {
+ QModelIndex index
+ = createIndex(getElementIndex(element), 2, element);
+ emit dataChanged(index, index);
+ });
+}
+
+HouseEventContainerModel::HouseEventContainerModel(Model *model,
+ QObject *parent)
+ : ElementContainerModel(model->houseEvents(), model, parent)
+{
+ for (Element *element : elements())
+ connectElement(element);
+}
+
+int HouseEventContainerModel::columnCount(const QModelIndex &parent) const
+{
+ return parent.isValid() ? 0 : 3;
+}
+
+QVariant HouseEventContainerModel::headerData(int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
+ return ElementContainerModel::headerData(section, orientation, role);
+
+ switch (section) {
+ case 0:
+ return tr("ID");
+ case 1:
+ return tr("State");
+ case 2:
+ return tr("Label");
+ }
+ GUI_ASSERT(false && "unexpected header section", {});
+}
+
+QVariant HouseEventContainerModel::data(const QModelIndex &index,
+ int role) const
+{
+ if (!index.isValid() || role != Qt::DisplayRole)
+ return {};
+ auto *houseEvent = static_cast<HouseEvent *>(index.internalPointer());
+
+ switch (index.column()) {
+ case 0:
+ return houseEvent->id();
+ case 1:
+ return houseEvent->state<QString>();
+ case 2:
+ return houseEvent->label();
+ }
+ GUI_ASSERT(false && "unexpected column", {});
+}
+
+void HouseEventContainerModel::connectElement(Element *element)
+{
+ ElementContainerModel::connectElement(element);
+ connect(static_cast<HouseEvent *>(element), &HouseEvent::stateChanged, this,
+ [this, element] {
+ QModelIndex index
+ = createIndex(getElementIndex(element), 1, element);
+ emit dataChanged(index, index);
+ });
+}
+
+GateContainerModel::GateContainerModel(Model *model, QObject *parent)
+ : ElementContainerModel(model->gates(), model, parent)
+{
+ for (Element *element : elements())
+ connectElement(element);
+}
+
+void GateContainerModel::connectElement(Element *element)
+{
+ ElementContainerModel::connectElement(element);
+ connect(static_cast<Gate *>(element), &Gate::formulaChanged, this,
+ [this, element] {
+ int row = getElementIndex(element);
+ emit dataChanged(createIndex(row, 1, element),
+ createIndex(row, 2, element));
+ /// @todo Track gate formula changes more precisely.
+ beginResetModel();
+ endResetModel();
+ });
+}
+
+int GateContainerModel::columnCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid())
+ return 4;
+ if (parent.parent().isValid())
+ return 0;
+ return 1;
+}
+
+int GateContainerModel::rowCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid())
+ return ElementContainerModel::rowCount(parent);
+ if (parent.parent().isValid())
+ return 0;
+ return static_cast<Gate *>(parent.internalPointer())->numArgs();
+}
+
+QModelIndex GateContainerModel::index(int row, int column,
+ const QModelIndex &parent) const
+{
+ if (!parent.isValid())
+ return ElementContainerModel::index(row, column, parent);
+ GUI_ASSERT(parent.parent().isValid() == false, {});
+ GUI_ASSERT(column == 0, {});
+
+ auto value = reinterpret_cast<std::uintptr_t>(parent.internalPointer());
+ GUI_ASSERT(value && !(value & m_parentMask), {});
+
+ return createIndex(row, column,
+ reinterpret_cast<void *>(value | m_parentMask));
+}
+
+QModelIndex GateContainerModel::parent(const QModelIndex &index) const
+{
+ GUI_ASSERT(index.isValid(), {});
+ auto value = reinterpret_cast<std::uintptr_t>(index.internalPointer());
+ GUI_ASSERT(value, {});
+ if (value & m_parentMask) {
+ auto *parent = reinterpret_cast<Gate *>(value & ~m_parentMask);
+ return createIndex(getElementIndex(parent), 0, parent);
+ }
+ return {};
+}
+
+QVariant GateContainerModel::headerData(int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
+ return ElementContainerModel::headerData(section, orientation, role);
+
+ switch (section) {
+ case 0:
+ return tr("ID");
+ case 1:
+ return tr("Connective");
+ case 2:
+ return tr("Args");
+ case 3:
+ return tr("Label");
+ }
+ GUI_ASSERT(false && "unexpected header section", {});
+}
+
+QVariant GateContainerModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || role != Qt::DisplayRole)
+ return {};
+ auto value = reinterpret_cast<std::uintptr_t>(index.internalPointer());
+ if (value & m_parentMask) {
+ auto *parent = reinterpret_cast<Gate *>(value & ~m_parentMask);
+ return QString::fromStdString(
+ ext::as<const mef::Event *>(parent->args().at(index.row()))->id());
+ }
+
+ auto *gate = static_cast<Gate *>(index.internalPointer());
+ switch (index.column()) {
+ case 0:
+ return gate->id();
+ case 1:
+ return gate->type<QString>();
+ case 2:
+ return gate->numArgs();
+ case 3:
+ return gate->label();
+ }
+ GUI_ASSERT(false && "unexpected column", {});
+}
+
+} // namespace model
+} // namespace gui
+} // namespace scram
diff --git a/gui/elementcontainermodel.h b/gui/elementcontainermodel.h
new file mode 100644
index 0000000..4abd6a7
--- /dev/null
+++ b/gui/elementcontainermodel.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file elementcontainermodel.h
+/// The table model for elements.
+
+#ifndef ELEMENTCONTAINERMODEL_H
+#define ELEMENTCONTAINERMODEL_H
+
+#include <cstdint>
+
+#include <unordered_map>
+#include <vector>
+
+#include <QAbstractItemModel>
+#include <QSortFilterProxyModel>
+
+#include "src/element.h"
+#include "src/event.h"
+
+#include "model.h"
+
+namespace scram {
+namespace gui {
+namespace model {
+
+/// The model to list elements in a table.
+class ElementContainerModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ int rowCount(const QModelIndex &parent) const override;
+
+protected:
+ /// @tparam T The container of smart pointers to elements.
+ template <class T>
+ explicit ElementContainerModel(const T &container, Model *model,
+ QObject *parent = nullptr);
+
+ /// Puts the element pointer into the index's internal pointer.
+ QModelIndex index(int row, int column,
+ const QModelIndex &parent) const override;
+
+ /// Assumes the table-layout and returns null index.
+ QModelIndex parent(const QModelIndex &) const override { return {}; }
+
+ /// @returns The element with the given index (row).
+ ///
+ /// @pre The index is valid.
+ Element *getElement(int index) const;
+
+ /// @returns The current index (row) of the element.
+ ///
+ /// @pre The element is in the table.
+ int getElementIndex(Element *element) const;
+
+ void addElement(Element *element);
+ void removeElement(Element *element);
+
+ const std::vector<Element *> elements() const { return m_elements; }
+
+protected:
+ /// Connects of the element change signals to the table modification.
+ /// The base implementation only handles signals coming from base element.
+ /// The derived classes need to override this function
+ /// and append more connections.
+ virtual void connectElement(Element *element);
+
+private:
+ std::vector<Element *> m_elements;
+ std::unordered_map<Element *, int> m_elementToIndex;
+};
+
+/// The proxy model allows sorting and filtering.
+class SortFilterProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ using QSortFilterProxyModel::QSortFilterProxyModel;
+
+ /// Keep the row indices sequential.
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role) const override
+ {
+ return sourceModel()->headerData(section, orientation, role);
+ }
+};
+
+class BasicEventContainerModel : public ElementContainerModel
+{
+ Q_OBJECT
+
+public:
+ using ItemModel = BasicEvent;
+ using DataType = mef::BasicEvent;
+
+ explicit BasicEventContainerModel(Model *model, QObject *parent = nullptr);
+
+ int columnCount(const QModelIndex &parent) const override;
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role) const override;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+
+private:
+ void connectElement(Element *element) final;
+};
+
+class HouseEventContainerModel : public ElementContainerModel
+{
+ Q_OBJECT
+
+public:
+ using ItemModel = HouseEvent;
+ using DataType = mef::HouseEvent;
+
+ explicit HouseEventContainerModel(Model *model, QObject *parent = nullptr);
+
+ int columnCount(const QModelIndex &parent) const override;
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role) const override;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+
+private:
+ void connectElement(Element *element) final;
+};
+
+/// Tree-view inside table.
+class GateContainerModel : public ElementContainerModel
+{
+ Q_OBJECT
+
+public:
+ using ItemModel = Gate;
+ using DataType = mef::Gate;
+
+ explicit GateContainerModel(Model *model, QObject *parent = nullptr);
+
+ int columnCount(const QModelIndex &parent) const override;
+ int rowCount(const QModelIndex &parent) const override;
+
+ /// The index for children embeds the parent information into the data.
+ QModelIndex index(int row, int column,
+ const QModelIndex &parent) const override;
+ QModelIndex parent(const QModelIndex &index) const override;
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role) const override;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+
+private:
+ static const std::uintptr_t m_parentMask = 1; ///< Tagged parent pointer.
+
+ void connectElement(Element *element) final;
+};
+
+} // namespace model
+} // namespace gui
+} // namespace scram
+
+#endif // ELEMENTCONTAINERMODEL_H
diff --git a/gui/event.cpp b/gui/event.cpp
deleted file mode 100644
index e185e8e..0000000
--- a/gui/event.cpp
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2016 Olzhas Rakhimov
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "event.h"
-
-#include <QFontMetrics>
-#include <QPainter>
-#include <QPainterPath>
-#include <QRectF>
-#include <QStyleOptionGraphicsItem>
-
-namespace scram {
-namespace gui {
-
-template <class T>
-Event::Event(const T &, QGraphicsView *view)
- : m_view(view), m_typeGraphics(Event::getTypeGraphics<T>(units()))
-{
- m_view->scene()->addItem(this);
- if (m_typeGraphics) {
- m_typeGraphics->setParentItem(this);
- m_typeGraphics->setPos(0, 5.5 * units().height());
- }
-}
-
-QSize Event::units() const
-{
- QFontMetrics font = m_view->fontMetrics();
- return {font.averageCharWidth(), font.height()};
-}
-
-void Event::setTypeGraphics(QGraphicsItem *item)
-{
- delete m_typeGraphics;
- m_typeGraphics = item;
- m_typeGraphics->setParentItem(this);
- m_typeGraphics->setPos(0, 5.5 * units().height());
-}
-
-QRectF Event::boundingRect() const
-{
- int w = units().width();
- int h = units().height();
- return QRectF(-8 * w, 0, 16 * w, 5.5 * h);
-}
-
-void Event::paint(QPainter *painter,
- const QStyleOptionGraphicsItem * /*option*/,
- QWidget * /*widget*/)
-{
- int w = units().width();
- int h = units().height();
- QRectF rect(-8 * w, 0, 16 * w, 3 * h);
- painter->drawRect(rect);
- painter->drawText(rect, Qt::AlignCenter | Qt::TextWordWrap, m_description);
-
- painter->drawLine(QPointF(0, 3 * h), QPointF(0, 4 * h));
-
- QRectF nameRect(-5 * w, 4 * h, 10 * w, h);
- painter->drawRect(nameRect);
- painter->drawText(nameRect, Qt::AlignCenter, m_name);
-
- painter->drawLine(QPointF(0, 5 * h), QPointF(0, 5.5 * h));
-}
-
-template <>
-QGraphicsItem *Event::getTypeGraphics<BasicEvent>(const QSize &units)
-{
- double r = 5 * units.width();
- double d = 2 * r;
- return new QGraphicsEllipseItem(-r, 0, d, d);
-}
-
-BasicEvent::BasicEvent(QGraphicsView *view) : Event(*this, view) {}
-
-IntermediateEvent::IntermediateEvent(QGraphicsView *view) : Event(*this, view)
-{
-}
-
-} // namespace gui
-} // namespace scram
diff --git a/gui/event.h b/gui/event.h
deleted file mode 100644
index 17d5a74..0000000
--- a/gui/event.h
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2016 Olzhas Rakhimov
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef EVENT_H
-#define EVENT_H
-
-#include <memory>
-
-#include <QGraphicsItem>
-#include <QGraphicsView>
-#include <QSize>
-
-#include "gate.h"
-
-namespace scram {
-namespace gui {
-
-/**
- * @brief The base class for probabilistic events in a fault tree.
- *
- * The base event item provides
- * only the boxes containing the name and description of the event.
- * A derived class must provide
- * the symbolic representation of its kind.
- *
- * The sizes are measured in units of character height and average width.
- * This class provides the reference units for derived classes to use.
- * All derived class shapes should stay within the allowed box limits
- * to make the fault tree structure layered.
- *
- * The derived classes must confine themselves in (10 width x 10 width) box.
- */
-class Event : public QGraphicsItem
-{
-public:
- /**
- * @brief Assigns the short name or ID for the event.
- *
- * @param name Identifying string name for the event.
- */
- void setName(QString name) { m_name = std::move(name); }
-
- /**
- * @return Identifying name string.
- * If the name has not been set,
- * the string is empty.
- */
- const QString &getName() const { return m_name; }
-
- /**
- * @brief Adds description to the event.
- *
- * @param desc Information about the event.
- * Empty string for events without descriptions.
- */
- void setDescription(QString desc) { m_description = std::move(desc); }
-
- /**
- * @return Description of the event.
- * Empty string if no description is provided.
- */
- const QString &getDescription() { return m_description; }
-
- QRectF boundingRect() const final;
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
- QWidget *widget) final;
-
-protected:
- /**
- * @brief Assigns an event to a presentation view.
- *
- * The graphical representation of the derived type
- * is deduced by calling getTypeGraphics.
- *
- * @tparam T The derived class type.
- *
- * @param view The host view.
- */
- template <class T> Event(const T &, QGraphicsView *view);
-
- /**
- * @brief Provides the graphical representation of the derived type.
- *
- * @tparam T The derived class type.
- *
- * @param units The unit height and width for drawings.
- *
- * @return Graphics item to be attached to the event graphics.
- * nullptr if the item is undefined upon construction (default).
- *
- * @post The item is unowned and allocated on the heap
- * in order to be owned by the base event graphics item.
- */
- template <class T>
- static QGraphicsItem *getTypeGraphics(const QSize & /*units*/)
- {
- return nullptr;
- }
-
- /**
- * @return The graphics of the derived class.
- */
- QGraphicsItem *getTypeGraphics() const { return m_typeGraphics; }
-
- /**
- * @brief Releases the current derived class item,
- * and sets the new one.
- *
- * @param item The new item to represent the derived type.
- */
- void setTypeGraphics(QGraphicsItem *item);
-
- /**
- * @return Unit width (x) and height (y) for shapes.
- */
- QSize units() const;
-
-private:
- QGraphicsView *m_view; ///< The host view.
- QString m_name; ///< Identifying name of the event.
- QString m_description; ///< Description of the event.
- QGraphicsItem *m_typeGraphics; ///< The graphics of the derived type.
-};
-
-/**
- * @brief Representation of a fault tree basic event.
- */
-class BasicEvent : public Event
-{
-public:
- /**
- * @param view The host view.
- */
- explicit BasicEvent(QGraphicsView *view);
-};
-
-/**
- * @brief Intermediate fault events with gates.
- */
-class IntermediateEvent : public Event
-{
-public:
- /**
- * @param view The host view.
- */
- explicit IntermediateEvent(QGraphicsView *view);
-
- /**
- * @brief Sets the Boolean logic for the intermediate event inputs.
- *
- * @param gate The logic gate of the intermediate event.
- */
- void setGate(std::unique_ptr<Gate> gate)
- {
- setTypeGraphics(gate.release());
- }
-
- /**
- * @return The logic gate of the intermediate event.
- */
- Gate *getGate() const { return static_cast<Gate *>(getTypeGraphics()); }
-};
-
-} // namespace gui
-} // namespace scram
-
-#endif // EVENT_H
diff --git a/gui/eventdialog.cpp b/gui/eventdialog.cpp
new file mode 100644
index 0000000..7c5c983
--- /dev/null
+++ b/gui/eventdialog.cpp
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "eventdialog.h"
+
+#include <limits>
+
+#include <QCompleter>
+#include <QDoubleValidator>
+#include <QListView>
+#include <QObject>
+#include <QPushButton>
+#include <QRegularExpressionValidator>
+#include <QShortcut>
+#include <QStatusBar>
+
+#include <boost/range/algorithm.hpp>
+
+#include "src/element.h"
+#include "src/expression/constant.h"
+#include "src/expression/exponential.h"
+#include "src/ext/bits.h"
+#include "src/ext/find_iterator.h"
+#include "src/ext/variant.h"
+
+#include "guiassert.h"
+#include "overload.h"
+
+namespace scram {
+namespace gui {
+
+QString EventDialog::redBackground(QStringLiteral("background : red;"));
+QString EventDialog::yellowBackground(QStringLiteral("background : yellow;"));
+
+EventDialog::EventDialog(mef::Model *model, QWidget *parent)
+ : QDialog(parent), m_model(model), m_errorBar(new QStatusBar(this))
+{
+ static QRegularExpressionValidator nameValidator(
+ QRegularExpression(QStringLiteral(R"([[:alpha:]]\w*(-\w+)*)")));
+ static QDoubleValidator nonNegativeValidator(
+ 0, std::numeric_limits<double>::max(), 1000);
+ static QDoubleValidator probabilityValidator(0, 1, 1000);
+
+ setupUi(this);
+ gridLayout->addWidget(m_errorBar, gridLayout->rowCount(), 0,
+ gridLayout->rowCount(), gridLayout->columnCount());
+
+ nameLine->setValidator(&nameValidator);
+ constantValue->setValidator(&probabilityValidator);
+ exponentialRate->setValidator(&nonNegativeValidator);
+ addArgLine->setValidator(&nameValidator);
+ containerFaultTreeName->setValidator(&nameValidator);
+
+ connect(typeBox, OVERLOAD(QComboBox, currentIndexChanged, int),
+ [this](int index) {
+ switch (static_cast<EventType>(1 << index)) {
+ case HouseEvent:
+ GUI_ASSERT(typeBox->currentText() == tr("House event"), );
+ stackedWidgetType->setCurrentWidget(tabBoolean);
+ break;
+ case BasicEvent:
+ case Undeveloped:
+ case Conditional:
+ stackedWidgetType->setCurrentWidget(tabExpression);
+ break;
+ case Gate:
+ stackedWidgetType->setCurrentWidget(tabFormula);
+ break;
+ default:
+ GUI_ASSERT(false, );
+ }
+ /// @todo Implement container change.
+ if (index == ext::one_bit_index(EventType::Gate)) {
+ containerFaultTree->setEnabled(true);
+ containerFaultTree->setChecked(true);
+ containerModel->setEnabled(false);
+ if (m_fixContainerName)
+ containerFaultTreeName->setEnabled(false);
+ } else {
+ containerFaultTree->setEnabled(false);
+ containerModel->setEnabled(true);
+ containerModel->setChecked(true);
+ }
+ validate();
+ });
+ connect(expressionType, OVERLOAD(QComboBox, currentIndexChanged, int), this,
+ &EventDialog::validate);
+ connect(expressionBox, &QGroupBox::toggled, this, &EventDialog::validate);
+ connectLineEdits(
+ {nameLine, constantValue, exponentialRate, containerFaultTreeName});
+ connect(connectiveBox, OVERLOAD(QComboBox, currentIndexChanged, int),
+ [this](int index) {
+ voteNumberBox->setEnabled(index == mef::kVote);
+ validate();
+ });
+ connect(this, &EventDialog::formulaArgsChanged, [this] {
+ int numArgs = argsList->count();
+ int newMax = numArgs > 2 ? (numArgs - 1) : 2;
+ if (voteNumberBox->value() > newMax)
+ voteNumberBox->setValue(newMax);
+ voteNumberBox->setMaximum(newMax);
+ validate();
+ });
+ connect(addArgLine, &QLineEdit::returnPressed, this, [this] {
+ QString name = addArgLine->text();
+ if (name.isEmpty())
+ return;
+ addArgLine->setStyleSheet(yellowBackground);
+ if (hasFormulaArg(name)) {
+ m_errorBar->showMessage(
+ tr("The argument '%1' is already in formula.")
+ .arg(name));
+ return;
+ }
+ if (name == nameLine->text()) {
+ m_errorBar->showMessage(
+ tr("The argument '%1' would introduce a self-cycle.")
+ .arg(name));
+ return;
+ } else if (m_event) {
+ auto it = m_model->gates().find(name.toStdString());
+ if (it != m_model->gates().end() && checkCycle(it->get())) {
+ m_errorBar->showMessage(
+ tr("The argument '%1' would introduce a cycle.")
+ .arg(name));
+ return;
+ }
+ }
+ addArgLine->setStyleSheet({});
+ argsList->addItem(name);
+ emit formulaArgsChanged();
+ });
+ connect(addArgLine, &QLineEdit::textChanged,
+ [this] { addArgLine->setStyleSheet({}); });
+ stealTopFocus(addArgLine);
+ setupArgCompleter();
+ connect(addArgButton, &QPushButton::clicked, addArgLine,
+ &QLineEdit::returnPressed);
+ connect(removeArgButton, &QPushButton::clicked, argsList, [this] {
+ int rows = argsList->count();
+ if (rows == 0)
+ return;
+ if (argsList->currentItem())
+ delete argsList->currentItem();
+ else
+ delete argsList->takeItem(rows - 1);
+ emit formulaArgsChanged();
+ });
+ QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_Delete), argsList);
+ connect(shortcut, &QShortcut::activated, argsList, [this] {
+ if (argsList->currentItem()) {
+ delete argsList->currentItem();
+ emit formulaArgsChanged();
+ }
+ });
+
+ /// @todo Enable fault-tree as a container for events.
+ containerFaultTree->setEnabled(false);
+
+ // Ensure proper defaults.
+ GUI_ASSERT(typeBox->currentIndex() == 0, );
+ GUI_ASSERT(stackedWidgetType->currentIndex() == 0, );
+ GUI_ASSERT(expressionType->currentIndex() == 0, );
+ GUI_ASSERT(stackedWidgetExpressionData->currentIndex() == 0, );
+
+ // Validation triggers.
+ QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
+ GUI_ASSERT(okButton, );
+ okButton->setEnabled(false);
+ connect(this, &EventDialog::validated, okButton, &QPushButton::setEnabled);
+}
+
+std::vector<std::string> EventDialog::arguments() const
+{
+ std::vector<std::string> result;
+ for (int i = 0; i < argsList->count(); ++i)
+ result.push_back(
+ argsList->item(i)->data(Qt::DisplayRole).toString().toStdString());
+ return result;
+}
+
+bool EventDialog::hasFormulaArg(const QString &name)
+{
+ for (int i = 0; i < argsList->count(); ++i) {
+ if (argsList->item(i)->data(Qt::DisplayRole) == name)
+ return true;
+ }
+ return false;
+}
+
+bool EventDialog::checkCycle(const mef::Gate *gate)
+{
+ struct {
+ bool operator()(const mef::Event *) const { return false; }
+ bool operator()(const mef::Gate *arg) const
+ {
+ return m_self->checkCycle(arg);
+ }
+ EventDialog *m_self;
+ } visitor{this};
+
+ for (const mef::Formula::EventArg &arg : gate->formula().event_args()) {
+ if (ext::as<const mef::Element *>(arg) == m_event)
+ return true;
+ if (boost::apply_visitor(visitor, arg))
+ return true;
+ }
+ return false;
+}
+
+template <class T>
+mef::FaultTree *EventDialog::getFaultTree(const T *event) const
+{
+ // Find the fault tree of the first parent gate.
+ auto it
+ = boost::find_if(m_model->gates(), [&event](const mef::GatePtr &gate) {
+ return boost::find(gate->formula().event_args(),
+ mef::Formula::EventArg(const_cast<T *>(event)))
+ != gate->formula().event_args().end();
+ });
+ if (it == m_model->gates().end())
+ return nullptr;
+ return getFaultTree(it->get());
+}
+
+template <>
+mef::FaultTree *EventDialog::getFaultTree(const mef::Gate *event) const
+{
+ auto it = boost::find_if(
+ m_model->fault_trees(), [&event](const mef::FaultTreePtr &faultTree) {
+ return faultTree->gates().count(event->name());
+ });
+ GUI_ASSERT(it != m_model->fault_trees().end(), nullptr);
+ return it->get();
+}
+
+template <class T>
+void EventDialog::setupData(const model::Element &element, const T *origin)
+{
+ m_event = origin;
+ m_initName = element.id();
+ nameLine->setText(m_initName);
+ labelText->setPlainText(element.label());
+ m_fixContainerName = true;
+ mef::FaultTree *faultTree = getFaultTree(origin);
+ if (faultTree)
+ containerFaultTreeName->setText(
+ QString::fromStdString(faultTree->name()));
+ else
+ static_cast<QListView *>(typeBox->view())
+ ->setRowHidden(ext::one_bit_index(Gate), true);
+ /// @todo Allow type change w/ new fault tree creation.
+}
+
+void EventDialog::setupData(const model::HouseEvent &element)
+{
+ setupData(element, element.data());
+ typeBox->setCurrentIndex(ext::one_bit_index(HouseEvent));
+ stateBox->setCurrentIndex(element.state());
+}
+
+void EventDialog::setupData(const model::BasicEvent &element)
+{
+ setupData(element, element.data());
+ typeBox->setCurrentIndex(ext::one_bit_index(BasicEvent) + element.flavor());
+ auto &basicEvent = static_cast<const mef::BasicEvent &>(*element.data());
+ if (basicEvent.HasExpression()) {
+ expressionBox->setChecked(true);
+ if (auto *constExpr = dynamic_cast<mef::ConstantExpression *>(
+ &basicEvent.expression())) {
+ expressionType->setCurrentIndex(0);
+ constantValue->setText(QString::number(constExpr->value()));
+ } else {
+ auto *exponentialExpr = dynamic_cast<mef::Exponential *>(
+ &basicEvent.expression());
+ GUI_ASSERT(exponentialExpr, );
+ expressionType->setCurrentIndex(1);
+ exponentialRate->setText(
+ QString::number(exponentialExpr->args().front()->value()));
+ }
+ } else {
+ expressionBox->setChecked(false);
+ }
+}
+
+void EventDialog::setupData(const model::Gate &element)
+{
+ setupData(element, element.data());
+ typeBox->setCurrentIndex(ext::one_bit_index(Gate));
+
+ /// @todo Deal with type changes of the top gate.
+ if (getFaultTree(element.data())->top_events().front() == element.data()) {
+ static_cast<QListView *>(typeBox->view())
+ ->setRowHidden(ext::one_bit_index(HouseEvent), true);
+ static_cast<QListView *>(typeBox->view())
+ ->setRowHidden(ext::one_bit_index(BasicEvent), true);
+ static_cast<QListView *>(typeBox->view())
+ ->setRowHidden(ext::one_bit_index(Conditional), true);
+ static_cast<QListView *>(typeBox->view())
+ ->setRowHidden(ext::one_bit_index(Undeveloped), true);
+ }
+
+ connectiveBox->setCurrentIndex(element.type());
+ if (element.type() == mef::kVote)
+ voteNumberBox->setValue(element.voteNumber());
+ for (const mef::Formula::EventArg &arg : element.args())
+ argsList->addItem(
+ QString::fromStdString(ext::as<const mef::Event *>(arg)->id()));
+ emit formulaArgsChanged(); ///< @todo Bogus signal order conflicts.
+}
+
+std::unique_ptr<mef::Expression> EventDialog::expression() const
+{
+ GUI_ASSERT(tabExpression->isHidden() == false, nullptr);
+ if (expressionBox->isChecked() == false)
+ return nullptr;
+ switch (stackedWidgetExpressionData->currentIndex()) {
+ case 0:
+ GUI_ASSERT(constantValue->hasAcceptableInput(), nullptr);
+ return std::make_unique<mef::ConstantExpression>(
+ constantValue->text().toDouble());
+ case 1: {
+ GUI_ASSERT(exponentialRate->hasAcceptableInput(), nullptr);
+ std::unique_ptr<mef::Expression> rate(
+ new mef::ConstantExpression(exponentialRate->text().toDouble()));
+ auto *rate_arg = rate.get();
+ m_model->Add(std::move(rate));
+ return std::make_unique<mef::Exponential>(rate_arg,
+ &m_model->mission_time());
+ }
+ default:
+ GUI_ASSERT(false && "unexpected expression", nullptr);
+ }
+}
+
+void EventDialog::validate()
+{
+ m_errorBar->clearMessage();
+ emit validated(false);
+
+ if (nameLine->hasAcceptableInput() == false)
+ return;
+ QString name = nameLine->text();
+ nameLine->setStyleSheet(yellowBackground);
+ try {
+ if (name != m_initName) {
+ m_model->GetEvent(name.toStdString());
+ m_errorBar->showMessage(
+ tr("The event with name '%1' already exists.").arg(name));
+ return;
+ }
+ } catch (UndefinedElement &) {}
+
+ if (!tabFormula->isHidden() && hasFormulaArg(name)) {
+ m_errorBar->showMessage(
+ tr("Name '%1' would introduce a self-cycle.").arg(name));
+ return;
+ }
+ nameLine->setStyleSheet({});
+
+ if (!tabExpression->isHidden() && expressionBox->isChecked()) {
+ switch (stackedWidgetExpressionData->currentIndex()) {
+ case 0:
+ if (constantValue->hasAcceptableInput() == false)
+ return;
+ break;
+ case 1:
+ if (exponentialRate->hasAcceptableInput() == false)
+ return;
+ break;
+ default:
+ GUI_ASSERT(false && "unexpected expression", );
+ }
+ }
+
+ if (!tabFormula->isHidden()) {
+ int numArgs = argsList->count();
+ switch (static_cast<mef::Operator>(connectiveBox->currentIndex())) {
+ case mef::kNot:
+ case mef::kNull:
+ if (numArgs != 1) {
+ m_errorBar->showMessage(
+ tr("%1 connective requires a single argument.")
+ .arg(connectiveBox->currentText()));
+ return;
+ }
+ break;
+ case mef::kAnd:
+ case mef::kOr:
+ case mef::kNand:
+ case mef::kNor:
+ if (numArgs < 2) {
+ m_errorBar->showMessage(
+ tr("%1 connective requires 2 or more arguments.")
+ .arg(connectiveBox->currentText()));
+ return;
+ }
+ break;
+ case mef::kXor:
+ if (numArgs != 2) {
+ m_errorBar->showMessage(
+ tr("%1 connective requires exactly 2 arguments.")
+ .arg(connectiveBox->currentText()));
+ return;
+ }
+ break;
+ case mef::kVote:
+ if (numArgs <= voteNumberBox->value()) {
+ m_errorBar->showMessage(
+ tr("%1 connective requires at-least %2 arguments.")
+ .arg(connectiveBox->currentText(),
+ QString::number(voteNumberBox->value() + 1)));
+ return;
+ }
+ break;
+ default:
+ GUI_ASSERT(false && "unexpected connective", );
+ }
+ }
+
+ if (containerFaultTreeName->isEnabled()) {
+ if (containerFaultTreeName->hasAcceptableInput() == false)
+ return;
+ GUI_ASSERT(typeBox->currentIndex() == ext::one_bit_index(Gate), );
+ QString faultTreeName = containerFaultTreeName->text();
+ if (auto it
+ = ext::find(m_model->fault_trees(), faultTreeName.toStdString())) {
+ GUI_ASSERT((*it)->top_events().empty() == false, );
+ m_errorBar->showMessage(
+ tr("Fault tree '%1' is already defined with a top gate.")
+ .arg(faultTreeName));
+ containerFaultTreeName->setStyleSheet(yellowBackground);
+ return;
+ }
+ }
+ emit validated(true);
+}
+
+void EventDialog::connectLineEdits(std::initializer_list<QLineEdit *> lineEdits)
+{
+ for (QLineEdit *lineEdit : lineEdits) {
+ lineEdit->setStyleSheet(redBackground);
+ connect(lineEdit, &QLineEdit::textChanged, [this, lineEdit] {
+ if (lineEdit->hasAcceptableInput())
+ lineEdit->setStyleSheet({});
+ else
+ lineEdit->setStyleSheet(redBackground);
+ validate();
+ });
+ }
+}
+
+void EventDialog::stealTopFocus(QLineEdit *lineEdit)
+{
+ struct FocusGrabber : public QObject {
+ FocusGrabber(QObject *parent, QPushButton *okButton)
+ : QObject(parent), m_ok(okButton)
+ {
+ }
+ bool eventFilter(QObject *object, QEvent *event) override
+ {
+ if (event->type() == QEvent::FocusIn) {
+ m_ok->setDefault(false);
+ m_ok->setAutoDefault(false);
+ } else if (event->type() == QEvent::FocusOut) {
+ m_ok->setDefault(true);
+ m_ok->setAutoDefault(true);
+ }
+ return QObject::eventFilter(object, event);
+ }
+ QPushButton *m_ok;
+ };
+ lineEdit->installEventFilter(
+ new FocusGrabber(lineEdit, buttonBox->button(QDialogButtonBox::Ok)));
+}
+
+void EventDialog::setupArgCompleter()
+{
+ /// @todo Optimize the completion model.
+ QStringList allEvents;
+ allEvents.reserve(m_model->gates().size() + m_model->basic_events().size()
+ + m_model->house_events().size());
+ auto addEvents = [&allEvents](const auto &eventContainer) {
+ for (const auto &eventPtr : eventContainer)
+ allEvents.push_back(QString::fromStdString(eventPtr->id()));
+ };
+ addEvents(m_model->gates());
+ addEvents(m_model->basic_events());
+ addEvents(m_model->house_events());
+ auto *completer = new QCompleter(std::move(allEvents), this);
+ completer->setCaseSensitivity(Qt::CaseInsensitive);
+ addArgLine->setCompleter(completer);
+}
+
+} // namespace gui
+} // namespace scram
diff --git a/gui/eventdialog.h b/gui/eventdialog.h
new file mode 100644
index 0000000..c51bc05
--- /dev/null
+++ b/gui/eventdialog.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EVENTDIALOG_H
+#define EVENTDIALOG_H
+
+#include <initializer_list>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <QDialog>
+#include <QStatusBar>
+#include <QString>
+
+#include "ui_eventdialog.h"
+
+#include "src/event.h"
+#include "src/expression.h"
+#include "src/model.h"
+
+#include "model.h"
+
+namespace scram {
+namespace gui {
+
+/// The Dialog to create, present, and manipulate event data.
+///
+/// Only valid data is accepted by this dialog.
+/// That is, the dialog constrains the user input to be valid,
+/// and upon the acceptance, it guarantees that the data is valid
+/// for usage by the Model classes.
+///
+/// However, the requested data must be relevant to the current type.
+///
+/// @pre The model is normalized.
+class EventDialog : public QDialog, private Ui::EventDialog
+{
+ Q_OBJECT
+
+public:
+ enum EventType {
+ HouseEvent = 1 << 0,
+ BasicEvent = 1 << 1,
+ Undeveloped = 1 << 2,
+ Conditional = 1 << 3,
+ Gate = 1 << 4
+ };
+
+ explicit EventDialog(mef::Model *model, QWidget *parent = nullptr);
+
+ void setupData(const model::HouseEvent &element);
+ void setupData(const model::BasicEvent &element);
+ void setupData(const model::Gate &element);
+
+ /// @returns The type being defined by this dialog.
+ EventType currentType() const
+ {
+ return static_cast<EventType>(1 << typeBox->currentIndex());
+ }
+
+ /// @returns The name data.
+ QString name() const { return nameLine->text(); }
+ /// @returns The label data.
+ QString label() const
+ {
+ return labelText->toPlainText().simplified();
+ }
+
+ /// @returns The Boolean constant data.
+ bool booleanConstant() const { return stateBox->currentIndex(); }
+
+ /// @returns The probability expression for basic events.
+ /// nullptr if no expression is defined.
+ std::unique_ptr<mef::Expression> expression() const;
+
+ /// @returns The operator type for the formula.
+ mef::Operator connective() const
+ {
+ return static_cast<mef::Operator>(connectiveBox->currentIndex());
+ }
+
+ /// @returns The value for the vote number for formulas.
+ int voteNumber() const { return voteNumberBox->value(); }
+
+ /// @returns The set of formula argument ids.
+ std::vector<std::string> arguments() const;
+
+ /// @returns The fault tree container name.
+ std::string faultTree() const
+ {
+ return containerFaultTreeName->text().toStdString();
+ }
+
+signals:
+ void validated(bool valid);
+ void formulaArgsChanged();
+
+public slots:
+ void validate();
+
+private:
+ static QString redBackground;
+ static QString yellowBackground;
+
+ /// @returns true if the arg already list contains the string name.
+ bool hasFormulaArg(const QString &name);
+
+ /// @returns true if the arg would introduce a cycle.
+ ///
+ /// @pre The check is performed only for existing elements.
+ /// @pre The argument is not a self-cycle.
+ ///
+ /// @todo Optimize to be linear.
+ /// @todo Optimize with memoization.
+ bool checkCycle(const mef::Gate *gate);
+
+ /// @returns The fault tree the event belongs to.
+ /// nullptr if the event is unused in fault trees.
+ ///
+ /// @note Only gates are guaranteed to be in fault trees.
+ template <class T>
+ mef::FaultTree *getFaultTree(const T *event) const;
+
+ template <class T>
+ void setupData(const model::Element &element, const T *origin);
+ void connectLineEdits(std::initializer_list<QLineEdit *> lineEdits);
+ void stealTopFocus(QLineEdit *lineEdit); ///< Intercept the auto-default.
+
+ /// Sets up the formula argument completer.
+ void setupArgCompleter();
+
+ mef::Model *m_model;
+ QStatusBar *m_errorBar;
+ QString m_initName; ///< The name not validated for duplicates.
+ const mef::Element *m_event = nullptr; ///< Set only for existing events.
+ bool m_fixContainerName = false; ///< @todo Implement fault tree change.
+};
+
+} // namespace gui
+} // namespace scram
+
+#endif // EVENTDIALOG_H
diff --git a/gui/eventdialog.ui b/gui/eventdialog.ui
new file mode 100644
index 0000000..d664b14
--- /dev/null
+++ b/gui/eventdialog.ui
@@ -0,0 +1,549 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EventDialog</class>
+ <widget class="QDialog" name="EventDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>630</width>
+ <height>478</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Event Editor</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <layout class="QFormLayout" name="formLayout_3">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Label:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPlainTextEdit" name="labelText">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>146</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QAbstractScrollArea::AdjustToContents</enum>
+ </property>
+ <property name="tabChangesFocus">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="5" column="0" colspan="3">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="6" column="0" colspan="3">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <layout class="QFormLayout" name="formLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="nameLine"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="typeLabel">
+ <property name="text">
+ <string>Type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="typeBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>House event</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Basic event</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Undeveloped</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Conditional</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Gate</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0" rowspan="2">
+ <widget class="QStackedWidget" name="stackedWidgetType">
+ <property name="styleSheet">
+ <string notr="true">QTabWidget::pane { border: 0; }</string>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tabBoolean">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QFormLayout" name="formLayout_4">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>State:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="stateBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="statusTip">
+ <string>State combo</string>
+ </property>
+ <item>
+ <property name="text">
+ <string>False</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>True</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tabExpression">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="expressionBox">
+ <property name="title">
+ <string>Expression</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QComboBox" name="expressionType">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>Constant</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Exponential</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="stackedWidgetExpressionData">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="pageConstant">
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Value:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="constantValue"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="pageExponential">
+ <layout class="QFormLayout" name="formLayout_5">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Rate:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="exponentialRate"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tabFormula">
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <item>
+ <layout class="QFormLayout" name="formLayout_9">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Connective:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="connectiveBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>AND</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>OR</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>ATLEAST</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>XOR</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>NOT</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>NAND</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>NOR</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>NULL</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelMin">
+ <property name="text">
+ <string>Min:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="voteNumberBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>2</number>
+ </property>
+ <property name="maximum">
+ <number>2</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLineEdit" name="addArgLine">
+ <property name="placeholderText">
+ <string>Add argument with its ID</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QListWidget" name="argsList">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="addArgButton">
+ <property name="toolTip">
+ <string>Add Argument</string>
+ </property>
+ <property name="icon">
+ <iconset theme="list-add">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="removeArgButton">
+ <property name="toolTip">
+ <string>Remove Argument</string>
+ </property>
+ <property name="icon">
+ <iconset theme="list-remove">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Container</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <layout class="QFormLayout" name="formLayout_6">
+ <item row="1" column="0">
+ <widget class="QRadioButton" name="containerFaultTree">
+ <property name="text">
+ <string>Fault tree:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="containerFaultTreeName">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QRadioButton" name="containerModel">
+ <property name="text">
+ <string>Model</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="2" rowspan="4">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>17</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>nameLine</tabstop>
+ <tabstop>typeBox</tabstop>
+ <tabstop>labelText</tabstop>
+ <tabstop>stateBox</tabstop>
+ <tabstop>expressionBox</tabstop>
+ <tabstop>expressionType</tabstop>
+ <tabstop>constantValue</tabstop>
+ <tabstop>exponentialRate</tabstop>
+ <tabstop>connectiveBox</tabstop>
+ <tabstop>voteNumberBox</tabstop>
+ <tabstop>addArgLine</tabstop>
+ <tabstop>addArgButton</tabstop>
+ <tabstop>argsList</tabstop>
+ <tabstop>removeArgButton</tabstop>
+ <tabstop>containerModel</tabstop>
+ <tabstop>containerFaultTree</tabstop>
+ <tabstop>containerFaultTreeName</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>EventDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>expressionType</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>stackedWidgetExpressionData</receiver>
+ <slot>setCurrentIndex(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>90</x>
+ <y>151</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>141</x>
+ <y>193</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>EventDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>246</x>
+ <y>329</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>246</x>
+ <y>175</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>containerFaultTree</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>containerFaultTreeName</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>402</x>
+ <y>174</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>531</x>
+ <y>177</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/gui/guiassert.h b/gui/guiassert.h
new file mode 100644
index 0000000..16fa25d
--- /dev/null
+++ b/gui/guiassert.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUIASSERT_H
+#define GUIASSERT_H
+
+#include <QMessageBox>
+#include <QString>
+
+/// Assertion that avoids crashing the application.
+/// To simulate the standard assert (i.e., crash on error),
+/// define the environmental variable QT_FATAL_CRITICALS to non-empty value.
+///
+/// @param cond The condition under test.
+/// @param ret The return value if assertion fails.
+#define GUI_ASSERT(cond, ret) \
+ do { \
+ if (cond) \
+ break; \
+ qCritical("Assertion failure: %s in %s line %d", #cond, __FILE__, \
+ __LINE__); \
+ QMessageBox::critical( \
+ nullptr, QString::fromLatin1("Assertion Failure"), \
+ QString::fromLatin1("%1 in %2 line %3") \
+ .arg(QString::fromLatin1(#cond), QString::fromUtf8(__FILE__), \
+ QString::number(__LINE__))); \
+ return ret; \
+ } while (false)
+
+#endif // GUIASSERT_H
diff --git a/gui/images/scram128x128.png b/gui/images/scram128x128.png
new file mode 100644
index 0000000..c3c97cc
Binary files /dev/null and b/gui/images/scram128x128.png differ
diff --git a/gui/images/scram16x16.png b/gui/images/scram16x16.png
new file mode 100644
index 0000000..922c9b5
Binary files /dev/null and b/gui/images/scram16x16.png differ
diff --git a/gui/images/scram24x24.png b/gui/images/scram24x24.png
new file mode 100644
index 0000000..9672ed3
Binary files /dev/null and b/gui/images/scram24x24.png differ
diff --git a/gui/images/scram256x256.png b/gui/images/scram256x256.png
new file mode 100644
index 0000000..44a205d
Binary files /dev/null and b/gui/images/scram256x256.png differ
diff --git a/gui/images/scram32x32.png b/gui/images/scram32x32.png
new file mode 100644
index 0000000..9256b04
Binary files /dev/null and b/gui/images/scram32x32.png differ
diff --git a/gui/images/scram48x48.png b/gui/images/scram48x48.png
new file mode 100644
index 0000000..eb1af77
Binary files /dev/null and b/gui/images/scram48x48.png differ
diff --git a/gui/images/scram64x64.png b/gui/images/scram64x64.png
new file mode 100644
index 0000000..f20684d
Binary files /dev/null and b/gui/images/scram64x64.png differ
diff --git a/gui/images/scram_logo.svg b/gui/images/scram_logo.svg
new file mode 100644
index 0000000..5de3730
--- /dev/null
+++ b/gui/images/scram_logo.svg
@@ -0,0 +1,3991 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ id="svg2"
+ height="48"
+ width="48">
+ <defs
+ id="defs4">
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient10157">
+ <stop
+ id="stop10159"
+ offset="0"
+ style="stop-color:#d9ffff;stop-opacity:1;" />
+ </linearGradient>
+ <filter
+ height="1.2281379"
+ y="-0.11406891"
+ width="1.2108073"
+ x="-0.10540365"
+ id="filter10175">
+ <feGaussianBlur
+ id="feGaussianBlur10177"
+ stdDeviation="24.347563" />
+ </filter>
+ <clipPath
+ id="clipPath8252"
+ clipPathUnits="userSpaceOnUse">
+ <path
+ id="path8254"
+ d="m 15,118.36218 149,0 0,123 -149,0 z"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </clipPath>
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(-15,-193.36218)"
+ id="layer1">
+ <g
+ transform="matrix(0.32214765,0,0,0.32214765,10.167785,159.25744)"
+ clip-path="url(#clipPath8252)"
+ id="g7269">
+ <path
+ id="rect3057-6"
+ d="m -2652.9287,-534.19189 554.3845,0 0,512.270625 -554.3845,0 z"
+ style="fill:#d9ffff;fill-opacity:1;filter:url(#filter10175)"
+ transform="matrix(1.2859212,0,0,0.25634976,3423.785,252.86562)" />
+ <path
+ id="path2987-4-29"
+ d="m 178.71577,142.64208 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-5"
+ d="m 187.30412,135.33211 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-2"
+ d="m 178.73887,135.3844 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-00"
+ d="m 187.32896,142.65221 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-3"
+ d="m 178.51452,156.55943 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-9"
+ d="m 187.10288,149.24949 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-18"
+ d="m 178.53761,149.30178 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-19"
+ d="m 187.12771,156.56957 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-5"
+ d="m 196.09322,142.43722 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-3"
+ d="m 204.68156,135.12727 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-2"
+ d="m 196.11629,135.17954 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-5"
+ d="m 204.70639,142.44736 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-2"
+ d="m 195.89195,156.35462 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-5"
+ d="m 204.48033,149.04466 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-86"
+ d="m 195.91503,149.09694 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-77"
+ d="m 204.50515,156.36472 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-0-2"
+ d="m 178.80094,170.58294 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-9-29"
+ d="m 187.38931,163.27299 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-4"
+ d="m 178.82403,163.32528 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-06-1"
+ d="m 187.41415,170.59307 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-9"
+ d="m 178.59971,184.50032 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-3-6"
+ d="m 187.18805,177.19039 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-9"
+ d="m 178.6228,177.24267 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-9-8"
+ d="m 187.21287,184.51046 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-2"
+ d="m 196.17839,170.37808 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-5"
+ d="m 204.76675,163.06814 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-4-5"
+ d="m 196.20146,163.12044 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-4"
+ d="m 204.79158,170.38822 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-9"
+ d="m 195.97714,184.29548 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-1"
+ d="m 204.56549,176.98553 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-2"
+ d="m 196.00023,177.03782 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-50"
+ d="m 204.59032,184.3056 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-84-8"
+ d="m 212.90154,142.28542 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-96-3"
+ d="m 221.48992,134.97547 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-3-9"
+ d="m 212.92463,135.02777 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-7-3"
+ d="m 221.51474,142.29556 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-8-96"
+ d="m 212.7003,156.20282 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-7"
+ d="m 221.28866,148.89288 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-9-9"
+ d="m 212.72338,148.94516 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-1-9"
+ d="m 221.31351,156.21294 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-35-7"
+ d="m 230.27899,142.08058 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-9-6"
+ d="m 238.86737,134.77063 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-8-9"
+ d="m 230.30207,134.82292 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-4-3"
+ d="m 238.89219,142.09071 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-07-5"
+ d="m 230.07774,155.99797 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-7"
+ d="m 238.66609,148.68804 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-61-6"
+ d="m 230.10083,148.74031 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-5-6"
+ d="m 238.69092,156.0081 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-5"
+ d="m 212.98673,170.22631 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-2-8"
+ d="m 221.57508,162.91637 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-2"
+ d="m 213.00982,162.96865 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-5"
+ d="m 221.59993,170.23645 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-1-7-4"
+ d="m 212.78548,184.14369 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-3-4"
+ d="m 221.37384,176.83373 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-8-7-16"
+ d="m 212.80855,176.88601 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-2-1"
+ d="m 221.39867,184.15383 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-6-6"
+ d="m 230.36417,170.02146 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-0-3"
+ d="m 238.95252,162.71151 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-3"
+ d="m 230.38726,162.76382 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-5"
+ d="m 238.97735,170.03158 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-5-5"
+ d="m 230.16293,183.93886 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-7-3"
+ d="m 238.75128,176.62891 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-2"
+ d="m 230.18599,176.68118 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-4-82"
+ d="m 238.7761,183.94898 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-1-5"
+ d="m 247.42619,142.38116 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-2-3"
+ d="m 256.01453,135.07122 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-0-6"
+ d="m 247.44928,135.1235 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-0-1"
+ d="m 256.03936,142.39129 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-1-8"
+ d="m 247.22492,156.29856 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-4-6"
+ d="m 255.81329,148.98859 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-60-21"
+ d="m 247.24802,149.04087 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-7-4"
+ d="m 255.83812,156.30868 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-1-6"
+ d="m 247.51135,170.32203 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-7-2"
+ d="m 256.09972,163.01206 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-7-91"
+ d="m 247.53444,163.06436 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-7-5"
+ d="m 256.12456,170.33215 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-7-0"
+ d="m 247.31012,184.2394 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-3-3"
+ d="m 255.89846,176.92947 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-3-6"
+ d="m 247.3332,176.98176 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-5-4"
+ d="m 255.92328,184.24954 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-84-9-9"
+ d="m 264.23451,142.22937 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-96-9-2"
+ d="m 272.82288,134.91942 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-3-8-9"
+ d="m 264.25759,134.9717 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-7-1-3"
+ d="m 272.84771,142.23949 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-8-8-4"
+ d="m 264.03327,156.14674 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-2-4"
+ d="m 272.62165,148.83681 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-9-6-0"
+ d="m 264.05637,148.8891 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-1-6-5"
+ d="m 272.64647,156.15687 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-35-0-9"
+ d="m 281.61196,142.02453 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-9-3-6"
+ d="m 290.20031,134.71456 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-8-8-3"
+ d="m 281.63504,134.76685 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-4-0-4"
+ d="m 290.22514,142.03467 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-07-1-2"
+ d="m 281.41071,155.94191 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-2-8"
+ d="m 289.99906,148.63196 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-61-5-8"
+ d="m 281.43379,148.68424 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-5-0-7"
+ d="m 290.02392,155.95203 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-9-5"
+ d="m 264.3197,170.17024 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-2-4-8"
+ d="m 272.90807,162.86029 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-7-1"
+ d="m 264.34279,162.9126 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-8-2"
+ d="m 272.93291,170.18037 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-1-7-3-5"
+ d="m 264.11846,184.08762 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-3-5-7"
+ d="m 272.70681,176.77769 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-8-7-1-4"
+ d="m 264.14156,176.82996 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-2-2-44"
+ d="m 272.73163,184.09777 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-6-0-4"
+ d="m 281.69715,169.9654 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-0-1-4"
+ d="m 290.28548,162.65545 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-6-2"
+ d="m 281.72023,162.70774 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-4-9"
+ d="m 290.31033,169.97552 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-5-0-3"
+ d="m 281.4959,183.88278 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-7-6-0"
+ d="m 290.08424,176.57286 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-1-7"
+ d="m 281.51898,176.62512 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-4-8-78"
+ d="m 290.10908,183.89292 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-9-7"
+ d="m 178.35764,198.51978 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-84-3"
+ d="m 186.94599,191.20984 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-1-0"
+ d="m 178.38072,191.26211 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-4-3"
+ d="m 186.97081,198.52991 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-3-8"
+ d="m 195.73506,198.31493 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-9-2"
+ d="m 204.32341,191.00499 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-8-5"
+ d="m 195.75815,191.05726 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-8-2"
+ d="m 204.34825,198.32507 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-0-0-0"
+ d="m 178.64407,212.54326 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-9-8-3"
+ d="m 187.23241,205.23332 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-7-7"
+ d="m 178.66716,205.28562 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-06-7-2"
+ d="m 187.25723,212.5534 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-8-9"
+ d="m 178.44279,226.46066 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-38-7"
+ d="m 187.03116,219.15072 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-3-5"
+ d="m 178.46588,219.20299 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-7-3"
+ d="m 187.056,226.4708 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-10-4"
+ d="m 196.0215,212.33842 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-73-3"
+ d="m 204.60985,205.02848 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-4-4-2"
+ d="m 196.04458,205.08077 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-9-1"
+ d="m 204.63468,212.34854 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-6-1"
+ d="m 195.82022,226.25582 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-5-3"
+ d="m 204.4086,218.94589 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-1-2"
+ d="m 195.84332,218.99815 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-0-5"
+ d="m 204.43343,226.26595 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-8-9-3"
+ d="m 212.54339,198.16316 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-9-5"
+ d="m 221.13177,190.85321 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-9-68-1"
+ d="m 212.56649,190.90549 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-1-3-2"
+ d="m 221.1566,198.17328 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-07-4-2"
+ d="m 229.92084,197.95829 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-8-2"
+ d="m 238.50922,190.64836 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-61-4-6"
+ d="m 229.94393,190.70065 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-5-9-4"
+ d="m 238.53404,197.96845 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-92-0"
+ d="m 212.82982,212.18664 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-9-2-5-7"
+ d="m 221.4182,204.8767 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-5-0"
+ d="m 212.85291,204.92897 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-3-3"
+ d="m 221.44303,212.19677 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-7-33-4"
+ d="m 212.62858,226.10403 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-3-3-7-0"
+ d="m 221.21696,218.79406 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-7-4-0"
+ d="m 212.65168,218.84636 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-9-2-3-5"
+ d="m 221.24179,226.11416 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-6-8-3"
+ d="m 230.20726,211.98179 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-0-0-1"
+ d="m 238.79564,204.67186 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-8-0"
+ d="m 230.23035,204.72412 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-8-6"
+ d="m 238.82046,211.99193 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-5-06-7"
+ d="m 230.00603,225.89918 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-7-8-5"
+ d="m 238.59438,218.58923 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-5-19-0"
+ d="m 230.02911,218.64151 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-4-89-0"
+ d="m 238.61923,225.90931 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-1-7-4"
+ d="m 247.06804,198.25887 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-4-2-5"
+ d="m 255.65639,190.94893 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-60-2-4"
+ d="m 247.09113,191.00121 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-7-8-9"
+ d="m 255.68122,198.26903 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-1-2-5"
+ d="m 247.35448,212.28236 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-7-8-6"
+ d="m 255.94282,204.97242 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-7-9-4"
+ d="m 247.37756,205.0247 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-7-0-8"
+ d="m 255.96764,212.2925 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-7-7-2"
+ d="m 247.1532,226.19975 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-3-8-1"
+ d="m 255.74158,218.88982 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-3-1-6"
+ d="m 247.17629,218.94208 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-5-5-8"
+ d="m 255.76641,226.20988 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-8-8-8-3"
+ d="m 263.8764,198.10709 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-2-6-1"
+ d="m 272.46475,190.79714 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-9-6-1-4"
+ d="m 263.89944,190.84941 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-1-6-2-8"
+ d="m 272.48958,198.11722 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-07-1-4-4"
+ d="m 281.25382,197.90223 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-2-2-9"
+ d="m 289.84218,190.59231 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-61-5-5-2"
+ d="m 281.27689,190.64458 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-5-0-8-0"
+ d="m 289.86701,197.91237 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-9-6-7"
+ d="m 264.16282,212.1306 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-9-2-4-2-0"
+ d="m 272.75116,204.82065 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-7-6-6"
+ d="m 264.18589,204.87292 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-8-5-0"
+ d="m 272.77599,212.14071 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-7-3-3-7"
+ d="m 263.96154,226.04795 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-3-3-5-9-0"
+ d="m 272.54993,218.738 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-7-1-2-5"
+ d="m 263.98464,218.79029 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-9-2-2-4-6"
+ d="m 272.57476,226.05809 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-6-0-6-7"
+ d="m 281.54025,211.92573 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-0-1-1-3"
+ d="m 290.1286,204.61578 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-6-8-8"
+ d="m 281.56332,204.66809 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-4-2-4"
+ d="m 290.15344,211.93587 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-5-0-1-9"
+ d="m 281.33898,225.84311 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-7-6-1-2"
+ d="m 289.92736,218.53318 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-1-9-6"
+ d="m 281.36208,218.58545 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-4-8-7-5"
+ d="m 289.95219,225.85324 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-29-3"
+ d="m 598.5485,142.46309 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-5-03"
+ d="m 607.13685,135.15313 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-2-50"
+ d="m 598.57158,135.20542 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-00-76"
+ d="m 607.16168,142.47322 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-3-7"
+ d="m 598.34723,156.38047 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-9-83"
+ d="m 606.9356,149.07052 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-18-6"
+ d="m 598.37031,149.12279 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-19-4"
+ d="m 606.96043,156.39059 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-5-4"
+ d="m 615.92593,142.25824 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-3-1"
+ d="m 624.51429,134.9483 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-2-9"
+ d="m 615.949,135.00057 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-5-5"
+ d="m 624.53911,142.26838 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-2-57"
+ d="m 615.72467,156.17564 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-5-4"
+ d="m 624.31304,148.86567 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-86-7"
+ d="m 615.74776,148.91795 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-77-3"
+ d="m 624.33787,156.18577 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-0-2-40"
+ d="m 598.63366,170.40395 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-9-29-8"
+ d="m 607.22204,163.09401 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-4-81"
+ d="m 598.65675,163.1463 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-06-1-52"
+ d="m 607.24686,170.41409 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-9-3"
+ d="m 598.4324,184.32134 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-3-6-2"
+ d="m 607.02077,177.0114 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-9-4"
+ d="m 598.4555,177.06367 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-9-8-6"
+ d="m 607.04559,184.33147 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-2-6"
+ d="m 616.01109,170.19911 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-5-6"
+ d="m 624.59947,162.88916 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-4-5-2"
+ d="m 616.03418,162.94145 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-4-9"
+ d="m 624.6243,170.20924 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-9-04"
+ d="m 615.80985,184.11649 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-1-9"
+ d="m 624.39821,176.80656 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-2-3"
+ d="m 615.83295,176.85883 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-50-11"
+ d="m 624.42304,184.12663 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-84-8-99"
+ d="m 632.73426,142.10644 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-96-3-55"
+ d="m 641.32263,134.79649 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-3-9-56"
+ d="m 632.75734,134.84878 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-7-3-0"
+ d="m 641.34746,142.11658 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-8-96-5"
+ d="m 632.53301,156.02381 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-82-7-9"
+ d="m 641.12137,148.7139 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-9-9-46"
+ d="m 632.5561,148.76618 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-1-9-3"
+ d="m 641.14623,156.03395 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-35-7-4"
+ d="m 650.1117,141.90162 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-9-6-9"
+ d="m 658.70007,134.59164 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-8-9-0"
+ d="m 650.13479,134.64394 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-4-3-5"
+ d="m 658.72491,141.91174 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-07-5-39"
+ d="m 649.91045,155.81899 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-7-12"
+ d="m 658.49881,148.50906 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-61-6-88"
+ d="m 649.93355,148.56133 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-5-6-1"
+ d="m 658.52363,155.82912 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-5-8"
+ d="m 632.81944,170.04732 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-9-2-8-6"
+ d="m 641.4078,162.73737 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-0-2-0"
+ d="m 632.84253,162.78968 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-06-9-5-2"
+ d="m 641.43264,170.05746 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-7-4-4"
+ d="m 632.61821,183.96471 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-3-4-9"
+ d="m 641.20655,176.65478 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-7-16-0"
+ d="m 632.64127,176.70703 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-2-1-2"
+ d="m 641.23139,183.97483 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-6-6-2"
+ d="m 650.19688,169.84248 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-0-3-1"
+ d="m 658.78523,162.53254 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-4-1-3-5"
+ d="m 650.21997,162.58483 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-6-5-44"
+ d="m 658.81007,169.8526 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-5-5-42"
+ d="m 649.99565,183.75987 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-7-3-5"
+ d="m 658.58399,176.44993 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-5-2-04"
+ d="m 650.01869,176.50221 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-4-82-2"
+ d="m 658.60881,183.77 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-1-5-1"
+ d="m 667.2589,142.20217 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-2-3-2"
+ d="m 675.84725,134.89224 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-0-6-7"
+ d="m 667.28199,134.94452 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-0-1-79"
+ d="m 675.87209,142.21231 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-1-8-5"
+ d="m 667.05763,156.11957 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-4-6-54"
+ d="m 675.64601,148.80963 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-60-21-0"
+ d="m 667.08071,148.86188 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-7-4-3"
+ d="m 675.67083,156.12969 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-1-6-4"
+ d="m 667.34406,170.14306 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-7-2-1"
+ d="m 675.93244,162.83309 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-4-7-91-2"
+ d="m 667.36715,162.88538 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-7-5-2"
+ d="m 675.95727,170.15318 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-7-0-9"
+ d="m 667.14281,184.06043 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-3-3-8"
+ d="m 675.73117,176.75049 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-3-6-2"
+ d="m 667.16591,176.80277 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-5-4-2"
+ d="m 675.75599,184.07055 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-84-9-9-7"
+ d="m 684.06722,142.0504 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-96-9-2-8"
+ d="m 692.65559,134.74042 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-3-8-9-5"
+ d="m 684.09031,134.79271 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-7-1-3-8"
+ d="m 692.68043,142.06053 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-8-8-4-3"
+ d="m 683.86598,155.96777 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-82-2-4-9"
+ d="m 692.45436,148.65783 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-9-6-0-4"
+ d="m 683.88906,148.71011 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-1-6-5-4"
+ d="m 692.47918,155.97788 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-35-0-9-6"
+ d="m 701.44466,141.84555 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-9-3-6-98"
+ d="m 710.03303,134.53558 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-8-8-3-5"
+ d="m 701.46774,134.58787 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-4-0-4-66"
+ d="m 710.05785,141.85568 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-07-1-2-0"
+ d="m 701.24341,155.76294 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-63-2-8-7"
+ d="m 709.83176,148.45299 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-61-5-8-2"
+ d="m 701.26651,148.50526 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-5-0-7-7"
+ d="m 709.85662,155.77306 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-0-4-9-5-2"
+ d="m 684.15241,169.99125 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-2-4-8-6"
+ d="m 692.74079,162.68131 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-0-7-1-47"
+ d="m 684.1755,162.73361 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-06-9-8-2-1"
+ d="m 692.76561,170.00139 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-7-3-5-3"
+ d="m 683.95117,183.90863 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-3-5-7-21"
+ d="m 692.53952,176.5987 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-7-1-4-63"
+ d="m 683.97425,176.65099 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-2-2-44-83"
+ d="m 692.56434,183.91877 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-6-0-4-28"
+ d="m 701.52984,169.7864 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-0-1-4-6"
+ d="m 710.1182,162.47648 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-4-1-6-2-0"
+ d="m 701.55293,162.52876 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-6-4-9-1"
+ d="m 710.14305,169.79653 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-5-0-3-2"
+ d="m 701.32861,183.7038 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-7-6-0-6"
+ d="m 709.91695,176.39386 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-5-1-7-0"
+ d="m 701.3517,176.44614 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-4-8-78-5"
+ d="m 709.94178,183.71393 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-9-7-46"
+ d="m 598.19035,198.34079 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-84-3-7"
+ d="m 606.77869,191.03084 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-1-0-93"
+ d="m 598.21344,191.08313 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-4-3-4"
+ d="m 606.80353,198.35092 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-3-8-7"
+ d="m 615.56779,198.13596 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-9-2-88"
+ d="m 624.15614,190.82601 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-8-5-16"
+ d="m 615.59088,190.87829 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-8-2-3"
+ d="m 624.18096,198.14609 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-0-0-8"
+ d="m 598.47677,212.36429 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-9-8-3-7"
+ d="m 607.06513,205.05434 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-7-7-6"
+ d="m 598.49985,205.10664 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-06-7-2-1"
+ d="m 607.08995,212.37442 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-8-9-7"
+ d="m 598.27551,226.28167 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-3-38-7-2"
+ d="m 606.86389,218.97173 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-3-5-3"
+ d="m 598.29861,219.02402 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-9-7-3-7"
+ d="m 606.88873,226.29181 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-10-4-8"
+ d="m 615.85422,212.15944 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-73-3-5"
+ d="m 624.44257,204.8495 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-4-2-85"
+ d="m 615.87731,204.9018 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-9-1-8"
+ d="m 624.4674,212.16956 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-6-1-7"
+ d="m 615.65296,226.07683 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-5-3-3"
+ d="m 624.24133,218.76688 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-1-2-3"
+ d="m 615.67603,218.81918 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-0-5-1"
+ d="m 624.26615,226.08696 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-8-9-3-0"
+ d="m 632.37611,197.98417 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-9-5-8"
+ d="m 640.96449,190.67422 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-9-68-1-1"
+ d="m 632.3992,190.7265 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-1-3-2-0"
+ d="m 640.98931,197.9943 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-07-4-2-9"
+ d="m 649.75355,197.77932 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-8-2-2"
+ d="m 658.34193,190.46938 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-61-4-6-4"
+ d="m 649.77664,190.52167 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-5-9-4-9"
+ d="m 658.36675,197.78947 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-92-0-1"
+ d="m 632.66254,212.00766 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-2-5-7-5"
+ d="m 641.25092,204.69773 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-5-0-7"
+ d="m 632.68563,204.74999 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-3-3-3"
+ d="m 641.27576,212.0178 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-1-7-33-4-3"
+ d="m 632.4613,225.92505 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-3-7-0-4"
+ d="m 641.04967,218.61509 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-8-7-4-0-22"
+ d="m 632.48439,218.66737 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-2-3-5-0"
+ d="m 641.07449,225.93519 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-6-8-3-1"
+ d="m 650.03998,211.80281 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-0-0-1-2"
+ d="m 658.62836,204.49287 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-8-0-7"
+ d="m 650.06306,204.54516 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-8-6-7"
+ d="m 658.65319,211.81294 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-5-06-7-0"
+ d="m 649.83874,225.72021 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-7-8-5-2"
+ d="m 658.42708,218.41027 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-19-0-4"
+ d="m 649.86183,218.46252 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-4-89-0-1"
+ d="m 658.45193,225.73034 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-1-7-4-03"
+ d="m 666.90075,198.0799 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-4-2-5-7"
+ d="m 675.48909,190.76993 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-60-2-4-3"
+ d="m 666.92384,190.82222 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-7-8-9-9"
+ d="m 675.51392,198.09003 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-1-2-5-59"
+ d="m 667.18717,212.1034 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-7-8-6-7"
+ d="m 675.77554,204.79343 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-7-9-4-20"
+ d="m 667.21026,204.84572 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-7-0-8-28"
+ d="m 675.80036,212.11349 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-7-7-2-6"
+ d="m 666.98591,226.02076 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-3-8-1-7"
+ d="m 675.57427,218.71082 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-3-1-6-08"
+ d="m 667.00901,218.76311 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-5-5-8-1"
+ d="m 675.59911,226.03089 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-8-8-8-3-8"
+ d="m 683.7091,197.9281 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-2-6-1-3"
+ d="m 692.29745,190.61815 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-9-6-1-4-0"
+ d="m 683.73216,190.67044 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-1-6-2-8-3"
+ d="m 692.32229,197.93823 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-07-1-4-4-6"
+ d="m 701.08654,197.72325 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-63-2-2-9-2"
+ d="m 709.67489,190.41331 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-61-5-5-2-0"
+ d="m 701.10961,190.4656 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-5-0-8-0-2"
+ d="m 709.69972,197.73339 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-0-4-9-6-7-84"
+ d="m 683.99553,211.9516 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-2-4-2-0-1"
+ d="m 692.58388,204.64165 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-0-7-6-6-8"
+ d="m 684.01859,204.69394 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-8-5-0-3"
+ d="m 692.6087,211.96172 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-7-3-3-7-7"
+ d="m 683.79426,225.86898 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-3-5-9-0-58"
+ d="m 692.38264,218.55903 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-7-1-2-5-1"
+ d="m 683.81735,218.61131 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-2-2-4-6-7"
+ d="m 692.40747,225.87912 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-6-0-6-7-0"
+ d="m 701.37297,211.74675 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-0-1-1-3-4"
+ d="m 709.96131,204.43681 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-4-1-6-8-8-8"
+ d="m 701.39603,204.48909 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-6-4-2-4-8"
+ d="m 709.98614,211.75689 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-5-0-1-9-13"
+ d="m 701.1717,225.66413 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-7-6-1-2-6"
+ d="m 709.76008,218.35421 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-5-1-9-6-7"
+ d="m 701.19478,218.40647 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-4-8-7-5-8"
+ d="m 709.7849,225.67427 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-29-3-5"
+ d="m 326.34166,142.64208 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-5-03-5"
+ d="m 334.93001,135.33211 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-2-50-5"
+ d="m 326.36476,135.3844 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-00-76-6"
+ d="m 334.95483,142.65221 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-3-7-6"
+ d="m 326.1404,156.55943 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-9-83-3"
+ d="m 334.72878,149.24949 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-18-6-0"
+ d="m 326.16349,149.30178 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-19-4-0"
+ d="m 334.75361,156.56957 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-2-5-4-8"
+ d="m 343.7191,142.43722 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-3-1-8"
+ d="m 352.30745,135.12727 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-2-9-2"
+ d="m 343.74216,135.17954 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-5-5-3"
+ d="m 352.33228,142.44736 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-2-57-0"
+ d="m 343.51783,156.35462 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-5-4-6"
+ d="m 352.10622,149.04466 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-86-7-5"
+ d="m 343.54092,149.09694 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-77-3-0"
+ d="m 352.13104,156.36472 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-0-2-40-9"
+ d="m 326.42683,170.58294 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-29-8-7"
+ d="m 335.01519,163.27299 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-0-4-81-2"
+ d="m 326.44992,163.32528 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-1-52-2"
+ d="m 335.04003,170.59307 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-9-3-8"
+ d="m 326.22559,184.50032 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-6-2-2"
+ d="m 334.81394,177.19039 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-9-4-9"
+ d="m 326.24868,177.24267 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-8-6-4"
+ d="m 334.83876,184.51046 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-2-6-4"
+ d="m 343.80427,170.37808 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-5-6-8"
+ d="m 352.39265,163.06814 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-6-4-5-2-2"
+ d="m 343.82736,163.12044 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-02-6-4-9-7"
+ d="m 352.41747,170.38822 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-9-04-2"
+ d="m 343.60302,184.29548 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-1-9-5"
+ d="m 352.19137,176.98553 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-2-3-8"
+ d="m 343.6261,177.03782 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-50-11-4"
+ d="m 352.2162,184.3056 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-84-8-99-7"
+ d="m 360.52742,142.28542 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-96-3-55-3"
+ d="m 369.1158,134.97547 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-3-9-56-9"
+ d="m 360.55052,135.02777 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-7-3-0-5"
+ d="m 369.14064,142.29556 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-8-96-5-1"
+ d="m 360.32618,156.20282 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-7-9-9"
+ d="m 368.91453,148.89288 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-9-9-46-0"
+ d="m 360.34927,148.94516 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-1-9-3-2"
+ d="m 368.93938,156.21294 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-35-7-4-8"
+ d="m 377.90486,142.08058 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-9-6-9-3"
+ d="m 386.49324,134.77063 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-8-9-0-1"
+ d="m 377.92794,134.82292 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-4-3-5-4"
+ d="m 386.51806,142.09071 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-07-5-39-3"
+ d="m 377.70363,155.99797 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-7-12-1"
+ d="m 386.29197,148.68804 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-61-6-88-0"
+ d="m 377.72671,148.74031 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-5-6-1-4"
+ d="m 386.3168,156.0081 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-5-8-0"
+ d="m 360.61261,170.22631 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-9-2-8-6-7"
+ d="m 369.20096,162.91637 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-2-0-9"
+ d="m 360.63571,162.96865 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-5-2-0"
+ d="m 369.22582,170.23645 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-7-4-4-8"
+ d="m 360.41136,184.14369 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-3-3-4-9-1"
+ d="m 368.99973,176.83373 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-7-16-0-4"
+ d="m 360.43442,176.88601 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-9-2-1-2-0"
+ d="m 369.02455,184.15383 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-6-6-2-7"
+ d="m 377.99004,170.02146 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-0-3-1-9"
+ d="m 386.57841,162.71151 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-3-5-5"
+ d="m 378.01314,162.76382 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-5-44-3"
+ d="m 386.60322,170.03158 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-5-5-42-8"
+ d="m 377.78881,183.93886 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-7-3-5-2"
+ d="m 386.37716,176.62891 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-5-2-04-3"
+ d="m 377.81188,176.68118 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-4-82-2-2"
+ d="m 386.402,183.94898 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-1-5-1-1"
+ d="m 395.05206,142.38116 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-2-3-2-0"
+ d="m 403.64041,135.07122 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-0-6-7-4"
+ d="m 395.07515,135.1235 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-0-1-79-0"
+ d="m 403.66524,142.39129 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-1-8-5-7"
+ d="m 394.8508,156.29856 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-4-6-54-5"
+ d="m 403.43918,148.98859 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-60-21-0-9"
+ d="m 394.87389,149.04087 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-7-4-3-0"
+ d="m 403.46401,156.30868 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-1-6-4-5"
+ d="m 395.13722,170.32203 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-7-2-1-1"
+ d="m 403.7256,163.01206 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-4-7-91-2-2"
+ d="m 395.16031,163.06436 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-7-5-2-0"
+ d="m 403.75043,170.33215 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-7-0-9-9"
+ d="m 394.93599,184.2394 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-3-3-8-8"
+ d="m 403.52434,176.92947 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-3-6-2-9"
+ d="m 394.95907,176.98176 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-5-4-2-1"
+ d="m 403.54916,184.24954 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-84-9-9-7-7"
+ d="m 411.86039,142.22937 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-96-9-2-8-3"
+ d="m 420.44877,134.91942 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-3-8-9-5-3"
+ d="m 411.88348,134.9717 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-7-1-3-8-0"
+ d="m 420.47359,142.23949 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-8-8-4-3-7"
+ d="m 411.65914,156.14674 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-82-2-4-9-6"
+ d="m 420.24752,148.83681 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-9-6-0-4-6"
+ d="m 411.68224,148.8891 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-1-6-5-4-0"
+ d="m 420.27236,156.15687 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-35-0-9-6-2"
+ d="m 429.23783,142.02453 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-9-3-6-98-8"
+ d="m 317.954,135.45224 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-8-8-3-5-9"
+ d="m 429.26092,134.76685 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-4-0-4-66-8"
+ d="m 317.97883,142.77234 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-07-1-2-0-9"
+ d="m 429.03658,155.94191 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-2-8-7-9"
+ d="m 317.75273,149.36962 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-61-5-8-2-9"
+ d="m 429.05967,148.68424 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-5-0-7-7-4"
+ d="m 317.77759,156.68971 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-9-5-2-2"
+ d="m 411.94558,170.17024 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-9-2-4-8-6-0"
+ d="m 420.53394,162.86029 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-7-1-47-8"
+ d="m 411.96867,162.9126 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-06-9-8-2-1-7"
+ d="m 420.55878,170.18037 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-1-7-3-5-3-8"
+ d="m 411.74433,184.08762 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-3-5-7-21-7"
+ d="m 420.33269,176.77769 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-8-7-1-4-63-3"
+ d="m 411.76743,176.82996 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-2-2-44-83-0"
+ d="m 420.35751,184.09777 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-6-0-4-28-7"
+ d="m 429.32302,169.9654 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-0-1-4-6-6"
+ d="m 318.03917,163.39313 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-6-2-0-4"
+ d="m 429.34611,162.70774 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-4-9-1-2"
+ d="m 318.06402,170.7132 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-5-0-3-2-1"
+ d="m 429.12176,183.88278 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-7-6-0-6-9"
+ d="m 317.83792,177.3105 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-1-7-0-1"
+ d="m 429.14485,176.62512 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-4-8-78-5-6"
+ d="m 317.86275,184.63059 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-9-7-46-0"
+ d="m 325.98352,198.51978 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-84-3-7-3"
+ d="m 334.57186,191.20984 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-1-0-93-3"
+ d="m 326.00659,191.26211 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-4-3-4-6"
+ d="m 334.59669,198.52991 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-3-8-7-2"
+ d="m 343.36095,198.31493 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-9-2-88-2"
+ d="m 351.9493,191.00499 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-8-5-16-1"
+ d="m 343.38404,191.05726 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-8-2-3-4"
+ d="m 351.97414,198.32507 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-0-0-0-8-3"
+ d="m 326.26994,212.54326 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-8-3-7-2"
+ d="m 334.8583,205.23332 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-0-7-7-6-6"
+ d="m 326.29304,205.28562 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-7-2-1-0"
+ d="m 334.88312,212.5534 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-1-8-9-7-5"
+ d="m 326.06867,226.46066 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-38-7-2-4"
+ d="m 334.65706,219.15072 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-8-3-5-3-9"
+ d="m 326.09176,219.20299 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-7-3-7-6"
+ d="m 334.68188,226.4708 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-10-4-8-0"
+ d="m 343.64738,212.33842 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-4-73-3-5-8"
+ d="m 352.23573,205.02848 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-6-4-4-2-85-2"
+ d="m 343.67046,205.08077 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-02-6-9-1-8-7"
+ d="m 352.26056,212.34854 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-0-6-1-7-4"
+ d="m 343.44611,226.25582 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-6-5-3-3-0"
+ d="m 352.03449,218.94589 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-6-1-2-3-7"
+ d="m 343.46921,218.99815 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-1-0-5-1-6"
+ d="m 352.05932,226.26595 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-8-9-3-0-0"
+ d="m 360.16928,198.16316 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-82-9-5-8-1"
+ d="m 368.75765,190.85321 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-9-68-1-1-2"
+ d="m 360.19236,190.90549 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-1-3-2-0-9"
+ d="m 368.78248,198.17328 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-07-4-2-9-3"
+ d="m 377.54671,197.95829 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-63-8-2-2-8"
+ d="m 386.13509,190.64836 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-61-4-6-4-5"
+ d="m 377.5698,190.70065 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-5-9-4-9-1"
+ d="m 386.15991,197.96845 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-0-4-92-0-1-5"
+ d="m 360.4557,212.18664 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-2-5-7-5-6"
+ d="m 369.04408,204.8767 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-0-5-0-7-6"
+ d="m 360.47878,204.92897 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-3-3-3-8"
+ d="m 369.0689,212.19677 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-1-7-33-4-3-8"
+ d="m 360.25447,226.10403 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-3-7-0-4-9"
+ d="m 368.84285,218.79406 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-8-7-4-0-22-8"
+ d="m 360.27755,218.84636 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-2-3-5-0-7"
+ d="m 368.86768,226.11416 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-6-8-3-1-0"
+ d="m 377.83314,211.98179 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-0-0-1-2-7"
+ d="m 386.42151,204.67186 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-4-1-8-0-7-9"
+ d="m 377.85623,204.72412 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-6-8-6-7-1"
+ d="m 386.44635,211.99193 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-5-06-7-0-5"
+ d="m 377.6319,225.89918 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-7-8-5-2-8"
+ d="m 386.22025,218.58923 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-5-19-0-4-6"
+ d="m 377.65498,218.64151 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-4-89-0-1-3"
+ d="m 386.24511,225.90931 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-1-7-4-03-9"
+ d="m 394.69392,198.25887 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-4-2-5-7-4"
+ d="m 403.28226,190.94893 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-60-2-4-3-5"
+ d="m 394.717,191.00121 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-7-8-9-9-3"
+ d="m 403.30709,198.26903 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-1-2-5-59-8"
+ d="m 394.98035,212.28236 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-7-8-6-7-2"
+ d="m 403.5687,204.97242 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-7-9-4-20-3"
+ d="m 395.00344,205.0247 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-7-0-8-28-9"
+ d="m 403.59352,212.2925 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-7-7-2-6-4"
+ d="m 394.77908,226.19975 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-3-8-1-7-8"
+ d="m 403.36746,218.88982 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-3-1-6-08-5"
+ d="m 394.80217,218.94208 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-5-5-8-1-1"
+ d="m 403.39228,226.20988 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-8-8-8-3-8-3"
+ d="m 411.50227,198.10709 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-2-6-1-3-2"
+ d="m 420.09061,190.79714 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-9-6-1-4-0-8"
+ d="m 411.52533,190.84941 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-1-6-2-8-3-1"
+ d="m 420.11544,198.11722 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-07-1-4-4-6-6"
+ d="m 428.8797,197.90223 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-2-2-9-2-4"
+ d="m 317.59586,191.32997 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-61-5-5-2-0-6"
+ d="m 428.90277,190.64458 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-5-0-8-0-2-7"
+ d="m 317.62069,198.65003 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-9-6-7-84-7"
+ d="m 411.7887,212.1306 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-2-4-2-0-1-9"
+ d="m 420.37704,204.82065 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-7-6-6-8-6"
+ d="m 411.81176,204.87292 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-8-5-0-3-4"
+ d="m 420.40187,212.14071 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-1-7-3-3-7-7-1"
+ d="m 411.58743,226.04795 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-3-3-5-9-0-58-9"
+ d="m 420.17581,218.738 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-8-7-1-2-5-1-2"
+ d="m 411.61051,218.79029 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-9-2-2-4-6-7-6"
+ d="m 420.20063,226.05809 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-6-0-6-7-0-3"
+ d="m 429.16613,211.92573 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-0-1-1-3-4-1"
+ d="m 317.88228,205.35346 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-6-8-8-8-5"
+ d="m 429.18919,204.66809 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-4-2-4-8-9"
+ d="m 317.90711,212.67354 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-5-0-1-9-13-7"
+ d="m 428.96487,225.84311 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-7-6-1-2-6-0"
+ d="m 317.68105,219.27085 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-1-9-6-7-2"
+ d="m 428.98796,218.58545 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-4-8-7-5-8-6"
+ d="m 317.70587,226.59092 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-29-3-7"
+ d="m 456.6464,142.46307 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-5-03-6"
+ d="m 465.23475,135.15313 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-2-50-2"
+ d="m 456.6695,135.20542 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-00-76-5"
+ d="m 465.25959,142.4732 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-3-7-3"
+ d="m 456.44514,156.38045 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-9-83-4"
+ d="m 465.03351,149.07052 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-18-6-07"
+ d="m 456.46822,149.12279 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-19-4-5"
+ d="m 465.05833,156.39059 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-5-4-5"
+ d="m 474.02384,142.25822 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-3-1-3"
+ d="m 482.61218,134.94829 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-2-9-1"
+ d="m 474.0469,135.00057 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-5-5-8"
+ d="m 482.63701,142.26836 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-2-57-8"
+ d="m 473.82257,156.17562 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-5-4-8"
+ d="m 482.41093,148.86567 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-86-7-0"
+ d="m 473.84566,148.91795 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-77-3-1"
+ d="m 482.43577,156.18574 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-0-2-40-7"
+ d="m 456.73156,170.40395 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-29-8-5"
+ d="m 465.31994,163.09401 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-4-81-0"
+ d="m 456.75466,163.1463 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-1-52-5"
+ d="m 465.34477,170.41409 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-9-3-9"
+ d="m 456.53033,184.32134 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-6-2-0"
+ d="m 465.11867,177.01139 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-9-4-93"
+ d="m 456.55341,177.06366 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-8-6-5"
+ d="m 465.1435,184.33147 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-2-6-42"
+ d="m 474.10899,170.19911 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-5-6-7"
+ d="m 482.69737,162.88916 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-4-5-2-26"
+ d="m 474.13208,162.94145 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-4-9-72"
+ d="m 482.72219,170.20924 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-9-04-20"
+ d="m 473.90777,184.11649 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-1-9-1"
+ d="m 482.49609,176.80656 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-2-3-1"
+ d="m 473.93085,176.85881 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-50-11-7"
+ d="m 482.52093,184.12663 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-84-8-99-5"
+ d="m 490.83216,142.10644 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-96-3-55-9"
+ d="m 499.42053,134.79649 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-3-9-56-1"
+ d="m 490.85525,134.84878 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-7-3-0-8"
+ d="m 499.44536,142.11658 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-8-96-5-17"
+ d="m 490.63091,156.02381 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-7-9-8"
+ d="m 499.21926,148.7139 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-9-9-46-6"
+ d="m 490.654,148.76615 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-1-9-3-6"
+ d="m 499.24411,156.03395 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-35-7-4-9"
+ d="m 508.2096,141.90159 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-9-6-9-9"
+ d="m 516.79796,134.59164 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-8-9-0-2"
+ d="m 508.23269,134.64394 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-4-3-5-6"
+ d="m 516.82279,141.91174 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-07-5-39-6"
+ d="m 508.00835,155.81899 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-7-12-5"
+ d="m 516.59671,148.50906 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-61-6-88-8"
+ d="m 508.03144,148.56133 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-5-6-1-40"
+ d="m 516.62154,155.82912 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-5-8-3"
+ d="m 490.91734,170.04732 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-2-8-6-78"
+ d="m 499.5057,162.73736 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-2-0-3"
+ d="m 490.94043,162.78965 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-5-2-1"
+ d="m 499.53055,170.05746 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-1-7-4-4-1"
+ d="m 490.7161,183.96469 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-3-3-4-9-2"
+ d="m 499.30445,176.65475 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-8-7-16-0-5"
+ d="m 490.73917,176.70703 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-2-1-2-7"
+ d="m 499.32927,183.97482 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-6-6-2-6"
+ d="m 508.29478,169.84248 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-0-3-1-2"
+ d="m 516.88313,162.53252 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-3-5-1"
+ d="m 508.31788,162.5848 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-5-44-6"
+ d="m 516.90796,169.8526 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-5-5-42-1"
+ d="m 508.09354,183.75987 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-7-3-5-1"
+ d="m 516.6819,176.4499 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-2-04-5"
+ d="m 508.1166,176.5022 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-4-82-2-7"
+ d="m 516.70672,183.76998 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-1-5-1-4"
+ d="m 525.3568,142.20217 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-2-3-2-8"
+ d="m 533.94514,134.89221 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-0-6-7-5"
+ d="m 525.3799,134.9445 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-0-1-79-6"
+ d="m 533.96998,142.21231 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-1-8-5-4"
+ d="m 525.15553,156.11954 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-4-6-54-9"
+ d="m 533.74391,148.80961 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-60-21-0-8"
+ d="m 525.17862,148.86188 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-7-4-3-4"
+ d="m 533.76873,156.12967 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-1-6-4-3"
+ d="m 525.44196,170.14303 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-7-2-1-2"
+ d="m 534.03033,162.83309 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-7-91-2-5"
+ d="m 525.46505,162.88538 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-7-5-2-3"
+ d="m 534.05515,170.15317 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-7-0-9-8"
+ d="m 525.24072,184.06043 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-3-3-8-3"
+ d="m 533.82907,176.75049 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-3-6-2-2"
+ d="m 525.2638,176.80277 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-5-4-2-7"
+ d="m 533.8539,184.07055 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-84-9-9-7-0"
+ d="m 542.16512,142.05038 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-96-9-2-8-6"
+ d="m 550.7535,134.74042 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-3-8-9-5-8"
+ d="m 542.18821,134.79271 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-7-1-3-8-01"
+ d="m 550.77834,142.06051 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-8-8-4-3-1"
+ d="m 541.96388,155.96777 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-82-2-4-9-9"
+ d="m 550.55226,148.65783 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-9-6-0-4-5"
+ d="m 541.98697,148.71011 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-1-6-5-4-3"
+ d="m 550.57708,155.97788 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-35-0-9-6-6"
+ d="m 559.54256,141.84553 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-9-3-6-98-3"
+ d="m 568.13094,134.53558 a 3.5429946,2.9042541 0 0 1 -3.543,2.90426 3.5429946,2.9042541 0 0 1 -3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-8-8-3-5-1"
+ d="m 559.56565,134.58787 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-4-0-4-66-5"
+ d="m 568.15576,141.85566 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-07-1-2-0-3"
+ d="m 559.34132,155.76293 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-63-2-8-7-4"
+ d="m 567.92966,148.45299 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-61-5-8-2-0"
+ d="m 559.36441,148.50526 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-5-0-7-7-3"
+ d="m 567.95452,155.77306 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-9-5-2-1"
+ d="m 542.25031,169.99125 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-9-2-4-8-6-7"
+ d="m 550.8387,162.68131 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-0-7-1-47-9"
+ d="m 542.2734,162.73359 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-06-9-8-2-1-0"
+ d="m 550.86352,170.00139 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-7-3-5-3-3"
+ d="m 542.04907,183.90863 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-3-3-5-7-21-5"
+ d="m 550.63742,176.59868 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-7-1-4-63-2"
+ d="m 542.07216,176.65096 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-9-2-2-44-83-9"
+ d="m 550.66224,183.91877 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-6-0-4-28-1"
+ d="m 559.62775,169.7864 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-0-1-4-6-2"
+ d="m 568.2161,162.47648 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-6-2-0-6"
+ d="m 559.65084,162.52876 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-6-4-9-1-1"
+ d="m 568.24095,169.79653 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-5-0-3-2-7"
+ d="m 559.42651,183.7038 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-7-6-0-6-0"
+ d="m 568.01485,176.39383 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-1-7-0-15"
+ d="m 559.4496,176.44613 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-4-8-78-5-2"
+ d="m 568.03968,183.71393 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-9-7-46-2"
+ d="m 456.28825,198.34079 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-84-3-7-9"
+ d="m 464.8766,191.03084 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-1-0-93-0"
+ d="m 456.31134,191.08313 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-4-3-4-5"
+ d="m 464.90143,198.35092 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-3-8-7-1"
+ d="m 473.66569,198.13594 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-9-2-88-8"
+ d="m 482.25404,190.82601 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-8-5-16-3"
+ d="m 473.68878,190.87829 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-8-2-3-6"
+ d="m 482.27886,198.14609 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-0-0-0-8-8"
+ d="m 456.57468,212.36429 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-8-3-7-8"
+ d="m 465.16304,205.05434 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-7-7-6-61"
+ d="m 456.59777,205.10664 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-7-2-1-6"
+ d="m 465.18786,212.37442 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-8-9-7-6"
+ d="m 456.37342,226.28167 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-38-7-2-7"
+ d="m 464.96178,218.97171 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-3-5-3-0"
+ d="m 456.39651,219.02399 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-7-3-7-4"
+ d="m 464.98662,226.29181 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-10-4-8-9"
+ d="m 473.95212,212.15944 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-73-3-5-89"
+ d="m 482.54046,204.8495 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-6-4-4-2-85-4"
+ d="m 473.97521,204.9018 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-9-1-8-9"
+ d="m 482.5653,212.16956 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-0-6-1-7-8"
+ d="m 473.75086,226.07683 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-5-3-3-7"
+ d="m 482.33923,218.76688 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-6-1-2-3-5"
+ d="m 473.77395,218.81916 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-0-5-1-64"
+ d="m 482.36405,226.08696 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-8-9-3-0-8"
+ d="m 490.474,197.98417 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-82-9-5-8-6"
+ d="m 499.06238,190.67421 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-9-68-1-1-8"
+ d="m 490.4971,190.72649 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-1-3-2-0-7"
+ d="m 499.08722,197.9943 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-07-4-2-9-8"
+ d="m 507.85145,197.77932 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-63-8-2-2-0"
+ d="m 516.43983,190.46938 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-61-4-6-4-9"
+ d="m 507.87453,190.52164 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-5-9-4-9-5"
+ d="m 516.46465,197.78947 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-0-4-92-0-1-6"
+ d="m 490.76044,212.00766 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-2-5-7-5-9"
+ d="m 499.34881,204.69769 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-5-0-7-0"
+ d="m 490.78353,204.74999 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-3-3-3-0"
+ d="m 499.37364,212.0178 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-1-7-33-4-3-1"
+ d="m 490.5592,225.92502 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-3-7-0-4-3"
+ d="m 499.14757,218.61509 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-8-7-4-0-22-5"
+ d="m 490.58229,218.66737 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-2-3-5-0-5"
+ d="m 499.17239,225.93517 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-6-8-3-1-8"
+ d="m 508.13788,211.80281 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-0-0-1-2-0"
+ d="m 516.72626,204.49284 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-8-0-7-1"
+ d="m 508.16097,204.54514 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-8-6-7-7"
+ d="m 516.75108,211.81294 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-5-06-7-0-7"
+ d="m 507.93663,225.72021 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-7-8-5-2-4"
+ d="m 516.52498,218.41026 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-19-0-4-67"
+ d="m 507.95971,218.46252 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-4-89-0-1-0"
+ d="m 516.54983,225.73031 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-1-7-4-03-7"
+ d="m 524.99865,198.07988 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-4-2-5-7-41"
+ d="m 533.587,190.76993 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-6-60-2-4-3-2"
+ d="m 525.02174,190.82222 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-7-8-9-9-1"
+ d="m 533.61183,198.09001 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-1-2-5-59-83"
+ d="m 525.28508,212.10337 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-7-8-6-7-5"
+ d="m 533.87343,204.79343 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-7-9-4-20-7"
+ d="m 525.30816,204.84572 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-7-0-8-28-1"
+ d="m 533.89826,212.11349 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-7-7-2-6-47"
+ d="m 525.08382,226.02076 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-3-8-1-7-6"
+ d="m 533.67218,218.71082 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-3-1-6-08-8"
+ d="m 525.10691,218.76311 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-5-1-5-5-8-1-2"
+ d="m 533.69702,226.03089 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-8-8-8-3-8-30"
+ d="m 541.80699,197.9281 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-82-2-6-1-3-27"
+ d="m 550.39535,190.61815 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-9-6-1-4-0-88"
+ d="m 541.83007,190.67044 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-1-6-2-8-3-5"
+ d="m 550.42018,197.93823 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-4-07-1-4-4-6-9"
+ d="m 559.18443,197.72325 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-63-2-2-9-2-7"
+ d="m 567.77279,190.41331 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-61-5-5-2-0-1"
+ d="m 559.20751,190.4656 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-5-0-8-0-2-3"
+ d="m 567.79762,197.73339 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-9-6-7-84-4"
+ d="m 542.09343,211.9516 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-9-2-4-2-0-1-7"
+ d="m 550.68178,204.64165 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-0-0-7-6-6-8-1"
+ d="m 542.11649,204.69391 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-06-9-8-5-0-3-3"
+ d="m 550.70661,211.96172 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-8-1-7-3-3-7-7-7"
+ d="m 541.89216,225.86898 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-3-3-5-9-0-58-1"
+ d="m 550.48053,218.55902 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-34-5-8-7-1-2-5-1-27"
+ d="m 541.91525,218.61131 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-9-2-2-4-6-7-5"
+ d="m 550.50536,225.87912 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-4-2-3-6-0-6-7-0-34"
+ d="m 559.47087,211.74675 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-8-4-0-1-1-3-4-4"
+ d="m 568.05921,204.43681 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-6-8-8-8-55"
+ d="m 559.49394,204.48909 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-02-6-6-4-2-4-8-1"
+ d="m 568.08405,211.75689 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-5-0-1-9-13-4"
+ d="m 559.2696,225.66413 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-23-6-8-6-7-6-1-2-6-1"
+ d="m 567.85797,218.35418 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-1-9-6-7-26"
+ d="m 559.29269,218.40645 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#1a1a1a" />
+ <path
+ id="path2987-54-19-0-5-1-4-8-7-5-8-63"
+ d="m 567.8828,225.67427 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-00"
+ d="m 37.94293,142.38894 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-81"
+ d="m 46.53125,135.079 a 3.5429946,2.9042541 0 0 1 -3.542995,2.90425 3.5429946,2.9042541 0 0 1 -3.542994,-2.90425 3.5429946,2.9042541 0 0 1 3.542994,-2.90426 3.5429946,2.9042541 0 0 1 3.542995,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-38"
+ d="m 37.965962,135.13127 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-1"
+ d="m 46.556108,142.39906 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-6"
+ d="m 37.741633,156.30633 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-98"
+ d="m 46.330024,148.99637 a 3.5429946,2.9042541 0 0 1 -3.542995,2.90425 3.5429946,2.9042541 0 0 1 -3.542994,-2.90425 3.5429946,2.9042541 0 0 1 3.542994,-2.90426 3.5429946,2.9042541 0 0 1 3.542995,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-0"
+ d="m 37.764735,149.04865 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-13"
+ d="m 46.35485,156.31647 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-2-9"
+ d="m 55.320314,142.1841 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-39"
+ d="m 63.908712,134.87415 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-6-3"
+ d="m 55.343424,134.92643 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-02-65"
+ d="m 63.933531,142.19422 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-5"
+ d="m 55.119096,156.10147 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-40"
+ d="m 63.707446,148.79151 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-5"
+ d="m 55.142167,148.8438 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-0"
+ d="m 63.732311,156.11161 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-6"
+ d="m 38.028075,170.3298 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-9-5"
+ d="m 46.616466,163.01987 a 3.5429946,2.9042541 0 0 1 -3.542995,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542995,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-0-5"
+ d="m 38.051185,163.07214 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-06-18"
+ d="m 46.641284,170.33993 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-1-96"
+ d="m 37.826857,184.24719 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-2"
+ d="m 46.415207,176.93725 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-8-2"
+ d="m 37.84992,176.98953 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-9-5"
+ d="m 46.440026,184.25732 a 3.5429946,2.9042541 0 0 1 -3.542995,2.90425 3.5429946,2.9042541 0 0 1 -3.542994,-2.90425 3.5429946,2.9042541 0 0 1 3.542994,-2.90426 3.5429946,2.9042541 0 0 1 3.542995,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-2-3-0"
+ d="m 55.405498,170.12498 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-4-52"
+ d="m 63.993849,162.81501 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-52"
+ d="m 55.4286,162.8673 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-02-6-0"
+ d="m 64.018714,170.13509 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-0-67"
+ d="m 55.204272,184.04235 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-6-2"
+ d="m 63.792622,176.7324 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-6-8"
+ d="m 55.227373,176.78469 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-1-00"
+ d="m 63.817448,184.05248 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-84-6"
+ d="m 72.12867,142.03231 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-96-8"
+ d="m 80.717061,134.72234 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90426 3.5429946,2.9042541 0 0 1 -3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542994,2.90425 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-3-3"
+ d="m 72.151782,134.77463 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542994,2.90425 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-7-0"
+ d="m 80.741888,142.04242 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-8-6"
+ d="m 71.927443,155.94969 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-4"
+ d="m 80.515803,148.63974 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-9-3"
+ d="m 71.950514,148.69203 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-1-7"
+ d="m 80.540622,155.95982 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-35-05"
+ d="m 89.506134,141.82746 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-9-2"
+ d="m 98.094485,134.51749 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90426 3.5429946,2.9042541 0 0 1 -3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542994,2.90425 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-6-8-0"
+ d="m 89.529197,134.56978 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542994,2.90425 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-02-4-7"
+ d="m 98.119302,141.8376 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-07-56"
+ d="m 89.304875,155.74483 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-1"
+ d="m 97.893225,148.4349 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-61-8"
+ d="m 89.327977,148.48718 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-5-1"
+ d="m 97.918044,155.75497 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-8"
+ d="m 72.213846,169.97317 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-9-2-56"
+ d="m 80.802206,162.66324 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-59"
+ d="m 72.236957,162.71551 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-2"
+ d="m 80.827025,169.9833 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-1-7-6"
+ d="m 72.012589,183.89056 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-3-3-52"
+ d="m 80.600987,176.58062 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-8-7-3"
+ d="m 72.035691,176.63291 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-9-2-21"
+ d="m 80.625805,183.90069 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-2-3-6-2"
+ d="m 89.591309,169.76832 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-4-0-2"
+ d="m 98.179661,162.45838 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-88"
+ d="m 89.61438,162.51067 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-87"
+ d="m 98.204487,169.77847 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-0-5-4"
+ d="m 89.390051,183.68571 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-6-7-0"
+ d="m 97.978402,176.37577 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-6-5-0"
+ d="m 89.413122,176.42807 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-1-4-5"
+ d="m 98.003227,183.69585 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-2-1-9"
+ d="m 106.6533,142.12802 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.542992,-2.90425 3.5429946,2.9042541 0 0 1 3.542992,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-2-9"
+ d="m 115.24166,134.81808 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,2.90425 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-6-0-5"
+ d="m 106.67637,134.87038 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.542991,-2.90425 3.5429946,2.9042541 0 0 1 3.542991,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-02-0-3"
+ d="m 115.2665,142.13815 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-1-0"
+ d="m 106.45204,156.04541 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.542991,-2.90425 3.5429946,2.9042541 0 0 1 3.542991,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-4-9"
+ d="m 115.04043,148.73546 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-60-9"
+ d="m 106.47514,148.78777 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.542997,-2.90425 3.5429946,2.9042541 0 0 1 3.542997,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-7-5"
+ d="m 115.06526,156.05554 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-1-1"
+ d="m 106.73848,170.06891 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.542999,-2.90425 3.5429946,2.9042541 0 0 1 3.542999,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-4-7-1"
+ d="m 115.32686,162.75895 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-7-1"
+ d="m 106.76157,162.81124 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.542991,-2.90425 3.5429946,2.9042541 0 0 1 3.542991,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-7-2"
+ d="m 115.35168,170.07906 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-0-7-3"
+ d="m 106.53722,183.9863 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.542997,-2.90425 3.5429946,2.9042541 0 0 1 3.542997,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-6-3-39"
+ d="m 115.12559,176.67633 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-6-3-7"
+ d="m 106.56033,176.72861 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.542994,-2.90425 3.5429946,2.9042541 0 0 1 3.542994,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-1-5-2"
+ d="m 115.15042,183.99641 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-84-9-3"
+ d="m 123.46164,141.97624 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-96-9-3"
+ d="m 132.05002,134.66628 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-3-8-0"
+ d="m 123.48473,134.71856 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,2.90425 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-7-1-4"
+ d="m 132.07484,141.98638 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-8-8-84"
+ d="m 123.2604,155.89364 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-2-3"
+ d="m 131.84875,148.58367 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-9-6-9"
+ d="m 123.28349,148.63596 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-1-6-24"
+ d="m 131.87357,155.90375 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-35-0-90"
+ d="m 140.83908,141.7714 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-9-3-5"
+ d="m 149.42746,134.46146 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-8-8-7"
+ d="m 140.86217,134.51372 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.543,2.90425 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-02-4-0-47"
+ d="m 149.45229,141.78153 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-07-1-6"
+ d="m 140.63784,155.68881 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-2-6"
+ d="m 149.22619,148.37883 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-61-5-3"
+ d="m 140.66093,148.43112 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-5-0-0"
+ d="m 149.25101,155.69892 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-9-9"
+ d="m 123.54683,169.91711 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-9-2-4-0"
+ d="m 132.13518,162.60717 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-7-8"
+ d="m 123.56992,162.65944 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-8-0"
+ d="m 132.16001,169.92726 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-1-7-3-1"
+ d="m 123.34556,183.83448 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-3-3-5-5"
+ d="m 131.93394,176.52455 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-8-7-1-1"
+ d="m 123.36865,176.57684 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-9-2-2-0"
+ d="m 131.95876,183.84462 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-2-3-6-0-3"
+ d="m 140.92427,169.71226 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-8-4-0-1-9"
+ d="m 149.51262,162.40231 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-6-5"
+ d="m 140.94736,162.45461 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-4-8"
+ d="m 149.53745,169.7224 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-0-5-0-9"
+ d="m 140.723,183.62965 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-6-7-6-6"
+ d="m 149.31137,176.3197 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-1-2"
+ d="m 140.74609,176.372 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-1-4-8-5"
+ d="m 149.3362,183.63978 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-9-76"
+ d="m 37.584753,198.26667 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-84-38"
+ d="m 46.173105,190.95671 a 3.5429946,2.9042541 0 0 1 -3.542995,2.90425 3.5429946,2.9042541 0 0 1 -3.542994,-2.90425 3.5429946,2.9042541 0 0 1 3.542994,-2.90426 3.5429946,2.9042541 0 0 1 3.542995,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-1-5"
+ d="m 37.607856,191.00899 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-4-9"
+ d="m 46.19797,198.27678 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-3-3"
+ d="m 54.962216,198.06181 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-9-0"
+ d="m 63.550566,190.75185 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-8-2"
+ d="m 54.985287,190.80415 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-8-5"
+ d="m 63.575384,198.07193 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-0-1"
+ d="m 37.871187,212.29012 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-9-8-5"
+ d="m 46.459547,204.98018 a 3.5429946,2.9042541 0 0 1 -3.542995,2.90425 3.5429946,2.9042541 0 0 1 -3.542994,-2.90425 3.5429946,2.9042541 0 0 1 3.542994,-2.90426 3.5429946,2.9042541 0 0 1 3.542995,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-7-77"
+ d="m 37.894306,205.03248 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-7-9"
+ d="m 46.484373,212.30027 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-1-8-4"
+ d="m 37.66993,226.20753 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-3-38-1"
+ d="m 46.258281,218.89758 a 3.5429946,2.9042541 0 0 1 -3.542995,2.90425 3.5429946,2.9042541 0 0 1 -3.542994,-2.90425 3.5429946,2.9042541 0 0 1 3.542994,-2.90426 3.5429946,2.9042541 0 0 1 3.542995,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-8-3-7"
+ d="m 37.693,218.94987 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-0-9-7-8"
+ d="m 46.283146,226.21765 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-2-3-10-5"
+ d="m 55.248619,212.08528 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-4-73-7"
+ d="m 63.836969,204.77533 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-4-8"
+ d="m 55.27169,204.82763 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-9-8"
+ d="m 63.861835,212.09541 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-0-6-4"
+ d="m 55.047351,226.00268 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-6-5-2"
+ d="m 63.635743,218.69273 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-6-1-3"
+ d="m 55.070463,218.74502 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-1-0-8"
+ d="m 63.660568,226.01281 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-8-9-4"
+ d="m 71.770533,197.91001 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-9-1"
+ d="m 80.358915,190.60008 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-9-68-3"
+ d="m 71.793636,190.65237 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-1-3-0"
+ d="m 80.383742,197.92015 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-8-4-07-4-4"
+ d="m 89.147947,197.70517 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-63-8-1"
+ d="m 97.736346,190.39522 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-61-4-7"
+ d="m 89.171058,190.44751 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-5-9-7"
+ d="m 97.761165,197.7153 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-0-4-92-2"
+ d="m 72.056967,211.93349 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-9-2-5-1"
+ d="m 80.645318,204.62356 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-0-0-5-3"
+ d="m 72.080038,204.67586 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-3-1"
+ d="m 80.670145,211.94364 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-1-7-33-3"
+ d="m 71.855709,225.85091 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-3-3-7-1"
+ d="m 80.44406,218.54096 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-8-7-4-5"
+ d="m 71.878812,218.59322 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-9-2-3-1"
+ d="m 80.468886,225.86102 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-2-3-6-8-5"
+ d="m 89.434398,211.72866 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-4-0-0-0"
+ d="m 98.022781,204.41873 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-1-8-5"
+ d="m 89.4575,204.47101 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-6-8-1"
+ d="m 98.047607,211.73879 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-0-5-06-2"
+ d="m 89.233171,225.64605 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-6-7-8-2"
+ d="m 97.821483,218.3361 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-6-5-19-1"
+ d="m 89.256234,218.3884 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-1-4-89-5"
+ d="m 97.846348,225.65618 a 3.5429946,2.9042541 0 0 1 -3.542994,2.90425 3.5429946,2.9042541 0 0 1 -3.542995,-2.90425 3.5429946,2.9042541 0 0 1 3.542995,-2.90426 3.5429946,2.9042541 0 0 1 3.542994,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-1-7-5"
+ d="m 106.29516,198.00576 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.542991,-2.90425 3.5429946,2.9042541 0 0 1 3.542991,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-4-2-3"
+ d="m 114.88352,190.69582 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-60-2-8"
+ d="m 106.31826,190.74807 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.542997,-2.90425 3.5429946,2.9042541 0 0 1 3.542997,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-7-8-92"
+ d="m 114.90835,198.01589 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-4-2-3-1-2-8"
+ d="m 106.5816,212.02922 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.542997,-2.90425 3.5429946,2.9042541 0 0 1 3.542997,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-4-7-8-4"
+ d="m 115.16994,204.71928 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-6-4-7-9-2"
+ d="m 106.60467,204.77157 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.542997,-2.90425 3.5429946,2.9042541 0 0 1 3.542997,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-02-6-7-0-7"
+ d="m 115.19478,212.03937 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-0-7-7-5"
+ d="m 106.38034,225.94661 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.542996,-2.90425 3.5429946,2.9042541 0 0 1 3.542996,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-6-3-8-8"
+ d="m 114.9687,218.63667 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-6-3-1-4"
+ d="m 106.40341,218.68896 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.542993,-2.90425 3.5429946,2.9042541 0 0 1 3.542993,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-1-5-5-0"
+ d="m 114.99354,225.95674 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-8-8-8-9"
+ d="m 123.10349,197.85396 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-82-2-6-4"
+ d="m 131.69187,190.54401 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-9-6-1-43"
+ d="m 123.12658,190.5963 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-1-6-2-5"
+ d="m 131.71669,197.86407 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-07-1-4-8"
+ d="m 140.48093,197.64911 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-8-63-2-2-4"
+ d="m 149.06931,190.33916 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-6-61-5-5-1"
+ d="m 140.50402,190.39145 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-5-0-8-03"
+ d="m 149.09414,197.65924 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-0-4-9-6-3"
+ d="m 123.38992,211.87744 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-9-2-4-2-4"
+ d="m 131.9783,204.56748 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-0-0-7-6-0"
+ d="m 123.41301,204.61979 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-54-19-06-9-8-5-6"
+ d="m 132.00312,211.88757 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-1-7-3-3-75"
+ d="m 123.18868,225.79483 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-6-3-3-5-9-5"
+ d="m 131.77703,218.4849 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-5-8-7-1-2-6"
+ d="m 123.21177,218.53717 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-9-2-2-4-0"
+ d="m 131.80188,225.80497 a 3.5429946,2.9042541 0 0 1 -3.54299,2.90425 3.5429946,2.9042541 0 0 1 -3.543,-2.90425 3.5429946,2.9042541 0 0 1 3.543,-2.90426 3.5429946,2.9042541 0 0 1 3.54299,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-2-3-6-0-6-9"
+ d="m 140.76736,211.67259 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-23-8-4-0-1-1-8"
+ d="m 149.35573,204.36266 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-34-6-4-1-6-8-3"
+ d="m 140.79045,204.41494 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-02-6-6-4-2-5"
+ d="m 149.38056,211.68273 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-4-8-4-0-5-0-1-7"
+ d="m 140.56612,225.58999 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-23-6-8-6-7-6-1-9"
+ d="m 149.15447,218.28004 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ <path
+ id="path2987-34-5-6-6-5-1-9-0"
+ d="m 140.58921,218.33234 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#000000" />
+ <path
+ id="path2987-54-19-0-5-1-4-8-7-4"
+ d="m 149.1793,225.60011 a 3.5429946,2.9042541 0 0 1 -3.543,2.90425 3.5429946,2.9042541 0 0 1 -3.54299,-2.90425 3.5429946,2.9042541 0 0 1 3.54299,-2.90426 3.5429946,2.9042541 0 0 1 3.543,2.90426 z"
+ style="fill:#b3b3b3" />
+ </g>
+ </g>
+ <g
+ transform="translate(-15,-193.36218)"
+ id="layer2" />
+</svg>
diff --git a/gui/images/scram_solid.svg b/gui/images/scram_solid.svg
new file mode 100644
index 0000000..c50e795
--- /dev/null
+++ b/gui/images/scram_solid.svg
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ id="svg3352"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ viewBox="0 0 16 16"
+ sodipodi:docname="scram_solid.svg"
+ style="enable-background:new">
+ <defs
+ id="defs3354" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="15.696216"
+ inkscape:cx="-0.263051"
+ inkscape:cy="9.009864"
+ inkscape:current-layer="layer2"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ showguides="false"
+ inkscape:window-width="1366"
+ inkscape:window-height="744"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0" />
+ <metadata
+ id="metadata3357">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 3"
+ sodipodi:insensitive="true"
+ transform="translate(-0.01501232,-0.00445503)">
+ <rect
+ style="opacity:1;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#00ffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect4358"
+ width="15"
+ height="15"
+ x="0.51501232"
+ y="0.50445503" />
+ <path
+ style="fill:#ff0000;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 7.0277228,2.5301979 4.5049505,4.9628712 4.459901,2.6653464 Z"
+ id="path4360"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ transform="translate(-0.01501232,-0.00445503)">
+ <g
+ id="g4320"
+ transform="translate(0.75457922,0.38292079)">
+ <rect
+ style="opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:#00ffff;stroke-width:2.20000005;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect4296"
+ width="6.7872634"
+ height="0.24473278"
+ x="4.2126107"
+ y="-3.1165953"
+ transform="scale(1,-1)" />
+ <rect
+ transform="scale(1,-1)"
+ y="-13.090512"
+ x="4.5126114"
+ height="0.25076446"
+ width="7.0421019"
+ id="rect4298"
+ style="opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:#00ffff;stroke-width:2.20000005;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:#00ffff;stroke-width:2.29999995;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect4300"
+ width="7.0421019"
+ height="0.25076446"
+ x="4.2626109"
+ y="-8.1379166"
+ transform="scale(1,-1)" />
+ <rect
+ y="4.1126108"
+ x="3.2083693"
+ height="0.34365892"
+ width="4.8334723"
+ id="rect4302"
+ style="opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:#00ffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(0,1,1,0,0,0)" />
+ <rect
+ transform="matrix(0,1,1,0,0,0)"
+ style="opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:#00ffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect4304"
+ width="4.9525981"
+ height="0.35656214"
+ x="8.1379166"
+ y="11.298151" />
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ id="path4379"
+ d="m 5.8339106,3.1158414 0.01126,2.4439356 1.238861,-1.2163366 z"
+ style="display:inline;fill:#00ffff;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-0.01501232,-0.00445503)">
+ <g
+ id="g4370">
+ <rect
+ transform="scale(1,-1)"
+ y="-2.7561991"
+ x="4.2126107"
+ height="0.24473278"
+ width="6.7872634"
+ id="rect3476"
+ style="opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:#4d4d4d;stroke-width:2.20000005;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:#4d4d4d;stroke-width:2.20000005;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect4278"
+ width="7.0421019"
+ height="0.25076446"
+ x="4.5126114"
+ y="-12.730116"
+ transform="scale(1,-1)" />
+ <rect
+ transform="scale(1,-1)"
+ y="-7.7775202"
+ x="4.2626109"
+ height="0.25076446"
+ width="7.0421019"
+ id="rect4280"
+ style="opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:#4d4d4d;stroke-width:2.29999995;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ transform="matrix(0,1,1,0,0,0)"
+ style="opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:#4d4d4d;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect4286"
+ width="4.8334723"
+ height="0.34365892"
+ x="2.8479731"
+ y="4.1126108" />
+ <rect
+ y="11.298151"
+ x="7.7775202"
+ height="0.35656214"
+ width="4.9525981"
+ id="rect4288"
+ style="opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:#4d4d4d;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(0,1,1,0,0,0)" />
+ </g>
+ <path
+ style="fill:#4d4d4d;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 5.0905938,2.3049503 0.01126,2.4439356 1.238861,-1.2163366 z"
+ id="path4362"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4377"
+ d="m 10.823141,12.835272 -0.01126,-2.443935 -1.2388611,1.216336 z"
+ style="fill:#4d4d4d;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="fill:#4d4d4d;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 10.575369,9.6818061 -0.01126,-2.443935 -1.2388614,1.216336 z"
+ id="path4382"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4384"
+ d="m 5.0004948,5.4584157 0.01126,2.4439356 1.238861,-1.2163366 z"
+ style="fill:#4d4d4d;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/gui/images/scram_solid128x128.png b/gui/images/scram_solid128x128.png
new file mode 100644
index 0000000..5bfd47a
Binary files /dev/null and b/gui/images/scram_solid128x128.png differ
diff --git a/gui/images/scram_solid16x16.png b/gui/images/scram_solid16x16.png
new file mode 100644
index 0000000..1a5435a
Binary files /dev/null and b/gui/images/scram_solid16x16.png differ
diff --git a/gui/images/scram_solid24x24.png b/gui/images/scram_solid24x24.png
new file mode 100644
index 0000000..dfb3a14
Binary files /dev/null and b/gui/images/scram_solid24x24.png differ
diff --git a/gui/images/scram_solid256x256.png b/gui/images/scram_solid256x256.png
new file mode 100644
index 0000000..9c250a6
Binary files /dev/null and b/gui/images/scram_solid256x256.png differ
diff --git a/gui/images/scram_solid32x32.png b/gui/images/scram_solid32x32.png
new file mode 100644
index 0000000..957d1d2
Binary files /dev/null and b/gui/images/scram_solid32x32.png differ
diff --git a/gui/images/scram_solid48x48.png b/gui/images/scram_solid48x48.png
new file mode 100644
index 0000000..dfec8fc
Binary files /dev/null and b/gui/images/scram_solid48x48.png differ
diff --git a/gui/images/scram_solid64x64.png b/gui/images/scram_solid64x64.png
new file mode 100644
index 0000000..9ed2092
Binary files /dev/null and b/gui/images/scram_solid64x64.png differ
diff --git a/gui/main.cpp b/gui/main.cpp
index 1973fbd..88ef9b5 100644
--- a/gui/main.cpp
+++ b/gui/main.cpp
@@ -15,86 +15,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <exception>
-#include <iostream>
-#include <string>
-#include <vector>
+/// @file main.cpp
+/// The proxy main() function
+/// with no link dependencies except for the SCRAM GUI (shared lib).
+/// The reason for this separation
+/// is weird linker failures on Windows with shared libraries.
-#include <QApplication>
+/// The actual entrance to the SCRAM GUI.
+extern int launchGui(int, char*[]);
-#include <boost/program_options.hpp>
-
-#include "mainwindow.h"
-
-namespace po = boost::program_options;
-
-namespace {
-
-/**
- * Parses the command-line arguments.
- *
- * @param[in] argc Count of arguments.
- * @param[in] argv Values of arguments.
- * @param[out] vm Variables map of program options.
- *
- * @returns 0 for success.
- * @returns 1 for errored state.
- * @returns -1 for information only state like help and version.
- */
-int parseArguments(int argc, char *argv[], po::variables_map *vm)
-{
- const char* usage = "Usage: scram-gui [options] [input-files]...";
- po::options_description desc("Options");
- desc.add_options()
- ("help", "Display this help message")
- ("config-file", po::value<std::string>(),
- "Project configuration file");
- try {
- po::store(po::parse_command_line(argc, argv, desc), *vm);
- } catch (std::exception &err) {
- std::cerr << "Option error: " << err.what() << "\n\n"
- << usage << "\n\n"
- << desc << "\n";
- return 1;
- }
-
- po::notify(*vm);
-
- po::positional_options_description p;
- p.add("input-files", -1);
-
- po::store(
- po::command_line_parser(argc, argv).options(desc).positional(p).run(),
- *vm);
- po::notify(*vm);
-
- // Process command-line arguments.
- if (vm->count("help")) {
- std::cout << usage << "\n\n" << desc << "\n";
- return -1;
- }
- return 0;
-}
-
-} // namespace
-
-int main(int argc, char *argv[])
-{
- QApplication a(argc, argv);
- scram::gui::MainWindow w;
- w.show();
-
- if (argc > 1) {
- po::variables_map vm;
- int ret = parseArguments(argc, argv, &vm);
- if (ret == 1)
- return 1;
- if (ret == -1)
- return 0;
- if (vm.count("config-file"))
- w.setConfig(vm["config-file"].as<std::string>());
- if (vm.count("input-files"))
- w.addInputFiles(vm["input-files"].as<std::vector<std::string>>());
- }
- return a.exec();
-}
+int main(int argc, char *argv[]) { return launchGui(argc, argv); }
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp
index 7c0e5cf..dde938c 100644
--- a/gui/mainwindow.cpp
+++ b/gui/mainwindow.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2016 Olzhas Rakhimov
+ * Copyright (C) 2015-2017 Olzhas Rakhimov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,32 +17,1380 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
+#include "ui_namedialog.h"
+#include "ui_startpage.h"
+
+#include <algorithm>
+#include <type_traits>
-#include <QAction>
#include <QApplication>
-#include <QGraphicsScene>
+#include <QCoreApplication>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QPrinter>
+#include <QProgressDialog>
+#include <QRegularExpression>
+#include <QSvgGenerator>
+#include <QTableView>
+#include <QTableWidget>
+#include <QtConcurrent>
+#include <QtOpenGL>
+
+#include <libxml++/libxml++.h>
+
+#include "src/config.h"
+#include "src/env.h"
+#include "src/error.h"
+#include "src/expression/constant.h"
+#include "src/expression/exponential.h"
+#include "src/ext/algorithm.h"
+#include "src/ext/find_iterator.h"
+#include "src/ext/variant.h"
+#include "src/initializer.h"
+#include "src/reporter.h"
+#include "src/serialization.h"
+#include "src/xml.h"
-#include "event.h"
+#include "elementcontainermodel.h"
+#include "diagram.h"
+#include "guiassert.h"
+#include "modeltree.h"
+#include "printable.h"
+#include "settingsdialog.h"
namespace scram {
namespace gui {
+class NameDialog : public QDialog, public Ui::NameDialog
+{
+public:
+ explicit NameDialog(QWidget *parent) : QDialog(parent)
+ {
+ setupUi(this);
+ /// @todo Provide validators from a central location.
+ static QRegularExpressionValidator nameValidator(
+ QRegularExpression(QStringLiteral(R"([[:alpha:]]\w*(-\w+)*)")));
+ nameLine->setValidator(&nameValidator);
+ }
+};
+
+class StartPage : public QWidget, public Ui::StartPage
+{
+public:
+ explicit StartPage(QWidget *parent = nullptr) : QWidget(parent)
+ {
+ setupUi(this);
+ }
+};
+
+class WaitDialog : public QProgressDialog
+{
+public:
+ explicit WaitDialog(QWidget *parent) : QProgressDialog(parent)
+ {
+ setFixedSize(size());
+ setWindowFlags(static_cast<Qt::WindowFlags>(
+ windowFlags() | Qt::MSWindowsFixedSizeDialogHint
+ | Qt::FramelessWindowHint));
+ setCancelButton(nullptr);
+ setRange(0, 0);
+ setMinimumDuration(0);
+ }
+
+private:
+ void keyPressEvent(QKeyEvent *event) override
+ {
+ if (event->key() == Qt::Key_Escape)
+ return event->accept();
+ QProgressDialog::keyPressEvent(event);
+ }
+};
+
+class DiagramView : public ZoomableView, public Printable
+{
+public:
+ using ZoomableView::ZoomableView;
+
+ void exportAs()
+ {
+ QString filename = QFileDialog::getSaveFileName(
+ this, tr("Export As"), QDir::homePath(),
+ tr("SVG files (*.svg);;All files (*.*)"));
+ QSize sceneSize = scene()->sceneRect().size().toSize();
+
+ QSvgGenerator generator;
+ generator.setFileName(filename);
+ generator.setSize(sceneSize);
+ generator.setViewBox(
+ QRect(0, 0, sceneSize.width(), sceneSize.height()));
+ generator.setTitle(filename);
+ QPainter painter;
+ painter.begin(&generator);
+ scene()->render(&painter);
+ painter.end();
+ }
+
+private:
+ void doPrint(QPrinter *printer) override
+ {
+ QPainter painter(printer);
+ painter.setRenderHint(QPainter::Antialiasing);
+ scene()->render(&painter);
+ }
+};
+
+namespace {
+
+/// @returns A new table item for data tables.
+QTableWidgetItem *constructTableItem(QVariant data)
+{
+ auto *item = new QTableWidgetItem;
+ item->setData(Qt::EditRole, std::move(data));
+ item->setFlags(item->flags() & ~Qt::ItemIsEditable);
+ return item;
+}
+
+} // namespace
+
MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent),
- ui(new Ui::MainWindow)
+ : QMainWindow(parent), ui(new Ui::MainWindow),
+ m_undoStack(new QUndoStack(this)),
+ m_percentValidator(QRegularExpression(QStringLiteral(R"([1-9]\d+%?)"))),
+ m_zoomBox(new QComboBox)
{
ui->setupUi(this);
- auto *aboutQtAction = new QAction(tr("About &Qt"), this);
- aboutQtAction->setStatusTip(tr("About the Qt toolkit"));
- connect(aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt);
- ui->menuHelp->addAction(aboutQtAction);
+ m_zoomBox->setEditable(true);
+ m_zoomBox->setEnabled(false);
+ m_zoomBox->setInsertPolicy(QComboBox::NoInsert);
+ m_zoomBox->setValidator(&m_percentValidator);
+ for (QAction *action : ui->menuZoom->actions()) {
+ m_zoomBox->addItem(action->text());
+ connect(action, &QAction::triggered, m_zoomBox, [action, this] {
+ m_zoomBox->setCurrentText(action->text());
+ });
+ }
+ m_zoomBox->setCurrentText(QStringLiteral("100%"));
+ ui->zoomToolBar->addWidget(m_zoomBox); // Transfer the ownership.
+
+ setupStatusBar();
+ setupActions();
+
+ auto *startPage = new StartPage;
+ connect(startPage->newModelButton, &QAbstractButton::clicked,
+ ui->actionNewModel, &QAction::trigger);
+ connect(startPage->openModelButton, &QAbstractButton::clicked,
+ ui->actionOpenFiles, &QAction::trigger);
+ connect(startPage->exampleModelsButton, &QAbstractButton::clicked, this,
+ [this]() {
+ openFiles(QString::fromStdString(Env::install_dir()
+ + "/share/scram/input"));
+ });
+ ui->tabWidget->addTab(startPage, startPage->windowIcon(),
+ startPage->windowTitle());
+
+ connect(ui->modelTree, &QTreeView::activated, this,
+ &MainWindow::activateModelTree);
+ connect(ui->reportTreeWidget, &QTreeWidget::itemActivated, this,
+ [this](QTreeWidgetItem *item) {
+ if (auto it = ext::find(m_reportActions, item))
+ it->second();
+ });
+ connect(ui->tabWidget, &QTabWidget::tabCloseRequested, this,
+ [this](int index) {
+ // Ensure show/hide order.
+ if (index == ui->tabWidget->currentIndex()) {
+ int num_tabs = ui->tabWidget->count();
+ if (num_tabs > 1) {
+ ui->tabWidget->setCurrentIndex(
+ index == (num_tabs - 1) ? index - 1 : index + 1);
+ }
+ }
+ auto *widget = ui->tabWidget->widget(index);
+ ui->tabWidget->removeTab(index);
+ delete widget;
+ });
+
+ connect(ui->actionSettings, &QAction::triggered, this, [this] {
+ SettingsDialog dialog(m_settings, this);
+ if (dialog.exec() == QDialog::Accepted)
+ m_settings = dialog.settings();
+ });
+ connect(ui->actionRun, &QAction::triggered, this, [this] {
+ GUI_ASSERT(m_model, );
+ if (m_settings.probability_analysis()
+ && ext::any_of(m_model->basic_events(),
+ [](const mef::BasicEventPtr &basicEvent) {
+ return !basicEvent->HasExpression();
+ })) {
+ QMessageBox::critical(this, tr("Validation Error"),
+ tr("Not all basic events have expressions "
+ "for probability analysis."));
+ return;
+ }
+ WaitDialog progress(this);
+ progress.setLabelText(tr("Running analysis..."));
+ auto analysis
+ = std::make_unique<core::RiskAnalysis>(m_model.get(), m_settings);
+ QFutureWatcher<void> futureWatcher;
+ connect(&futureWatcher, SIGNAL(finished()), &progress, SLOT(reset()));
+ futureWatcher.setFuture(
+ QtConcurrent::run([&analysis] { analysis->Analyze(); }));
+ progress.exec();
+ futureWatcher.waitForFinished();
+ resetReportWidget(std::move(analysis));
+ });
+
+ connect(this, &MainWindow::configChanged, [this] {
+ m_undoStack->clear();
+ setWindowTitle(QString::fromLatin1("%1[*]").arg(
+ QString::fromStdString(m_model->name())));
+ ui->actionSaveAs->setEnabled(true);
+ ui->actionAddElement->setEnabled(true);
+ ui->actionRenameModel->setEnabled(true);
+ ui->actionRun->setEnabled(true);
+ resetModelTree();
+ resetReportWidget(nullptr);
+ });
+ connect(m_undoStack, &QUndoStack::indexChanged, ui->reportTreeWidget,
+ [this] {
+ if (m_analysis)
+ resetReportWidget(nullptr);
+ });
+}
+
+MainWindow::~MainWindow() = default;
+
+void MainWindow::setConfig(const std::string &configPath,
+ std::vector<std::string> inputFiles)
+{
+ try {
+ Config config(configPath);
+ inputFiles.insert(inputFiles.begin(), config.input_files().begin(),
+ config.input_files().end());
+ mef::Initializer(inputFiles, config.settings());
+ addInputFiles(inputFiles);
+ m_settings = config.settings();
+ } catch (scram::Error &err) {
+ QMessageBox::critical(this, tr("Configuration Error"),
+ QString::fromUtf8(err.what()));
+ }
+}
+
+void MainWindow::addInputFiles(const std::vector<std::string> &inputFiles)
+{
+ static xmlpp::RelaxNGValidator validator(Env::install_dir()
+ + "/share/scram/gui.rng");
+
+ if (inputFiles.empty())
+ return;
+
+ auto validateWithGuiSchema = [](const std::string &xmlFile) {
+ std::unique_ptr<xmlpp::DomParser> parser
+ = ConstructDomParser(xmlFile);
+ try {
+ validator.validate(parser->get_document());
+ } catch (const xmlpp::validity_error &) {
+ throw ValidationError(
+ "Document failed validation against the GUI schema:\n"
+ + xmlpp::format_xml_error());
+ }
+ };
+
+ try {
+ std::vector<std::string> allInput = m_inputFiles;
+ allInput.insert(allInput.end(), inputFiles.begin(), inputFiles.end());
+ std::shared_ptr<mef::Model> newModel
+ = mef::Initializer(allInput, m_settings).model();
+
+ for (const std::string &inputFile : inputFiles)
+ validateWithGuiSchema(inputFile);
+
+ for (const mef::FaultTreePtr &faultTree : newModel->fault_trees()) {
+ if (faultTree->top_events().size() != 1) {
+ QMessageBox::critical(
+ this, tr("Initialization Error"),
+ tr("Fault tree '%1' must have a single top-gate.")
+ .arg(QString::fromStdString(faultTree->name())));
+ return;
+ }
+ }
+
+ m_model = std::move(newModel);
+ m_inputFiles = std::move(allInput);
+ } catch (scram::Error &err) {
+ QMessageBox::critical(this, tr("Initialization Error"),
+ QString::fromUtf8(err.what()));
+ return;
+ }
+
+ emit configChanged();
+}
+
+void MainWindow::setupStatusBar()
+{
+ m_searchBar = new QLineEdit;
+ m_searchBar->setHidden(true);
+ m_searchBar->setFrame(false);
+ m_searchBar->setMaximumHeight(m_searchBar->fontMetrics().height());
+ m_searchBar->setSizePolicy(QSizePolicy::MinimumExpanding,
+ QSizePolicy::Fixed);
+ m_searchBar->setPlaceholderText(tr("Find/Filter (Perl Regex)"));
+ ui->statusBar->addPermanentWidget(m_searchBar);
+}
+
+void MainWindow::setupActions()
+{
+ connect(ui->actionAboutQt, &QAction::triggered, qApp,
+ &QApplication::aboutQt);
+ connect(ui->actionAboutScram, &QAction::triggered, this, [this] {
+ QMessageBox::about(
+ this, tr("About SCRAM"),
+ tr("<h1>SCRAM %1</h1>"
+ "The GUI front-end for SCRAM,<br/>"
+ "a command-line risk analysis multi-tool.<br/><br/>"
+ "License: GPLv3+<br/>"
+ "Homepage: <a href=\"%2\">%2</a><br/>"
+ "Technical Support: <a href=\"%3\">%3</a><br/>"
+ "Bug Tracker: <a href=\"%4\">%4</a><br/><br/>"
+ "This program is distributed in the hope that it will be useful,"
+ " but WITHOUT ANY WARRANTY; without even the implied warranty of"
+ " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
+ "GNU General Public License for more details.")
+ .arg(QCoreApplication::applicationVersion(),
+ QString::fromLatin1("https://scram-pra.org"),
+ QString::fromLatin1("scram-users at googlegroups.com"),
+ QString::fromLatin1(
+ "https://github.com/rakhimov/scram/issues")));
+ });
+
+ // File menu actions.
+ ui->actionExit->setShortcut(QKeySequence::Quit);
+
+ ui->actionNewModel->setShortcut(QKeySequence::New);
+ connect(ui->actionNewModel, &QAction::triggered, this,
+ &MainWindow::createNewModel);
+
+ ui->actionOpenFiles->setShortcut(QKeySequence::Open);
+ connect(ui->actionOpenFiles, &QAction::triggered, this,
+ [this]() { openFiles(); });
+
+ ui->actionSave->setShortcut(QKeySequence::Save);
+ connect(ui->actionSave, &QAction::triggered, this, &MainWindow::saveModel);
+
+ ui->actionSaveAs->setShortcut(QKeySequence::SaveAs);
+ connect(ui->actionSaveAs, &QAction::triggered, this,
+ &MainWindow::saveModelAs);
+
+ ui->actionPrint->setShortcut(QKeySequence::Print);
+
+ connect(ui->actionExportReportAs, &QAction::triggered, this,
+ &MainWindow::exportReportAs);
+
+ // View menu actions.
+ ui->actionZoomIn->setShortcut(QKeySequence::ZoomIn);
+ ui->actionZoomOut->setShortcut(QKeySequence::ZoomOut);
+
+ // Edit menu actions.
+ ui->actionRemoveElement->setShortcut(QKeySequence::Delete);
+ connect(ui->actionAddElement, &QAction::triggered, this,
+ &MainWindow::addElement);
+ connect(ui->actionRenameModel, &QAction::triggered, this, [this] {
+ NameDialog nameDialog(this);
+ if (!m_model->HasDefaultName())
+ nameDialog.nameLine->setText(m_guiModel->id());
+ if (nameDialog.exec() == QDialog::Accepted) {
+ QString name = nameDialog.nameLine->text();
+ if (name != QString::fromStdString(m_model->GetOptionalName())) {
+ m_undoStack->push(new model::Model::SetName(std::move(name),
+ m_guiModel.get()));
+ }
+ }
+ });
+
+ // Undo/Redo actions
+ m_undoAction = m_undoStack->createUndoAction(this, tr("Undo:"));
+ m_undoAction->setShortcut(QKeySequence::Undo);
+ m_undoAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
+
+ m_redoAction = m_undoStack->createRedoAction(this, tr("Redo:"));
+ m_redoAction->setShortcut(QKeySequence::Redo);
+ m_redoAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-redo")));
+
+ ui->menuEdit->insertAction(ui->menuEdit->actions().front(), m_redoAction);
+ ui->menuEdit->insertAction(m_redoAction, m_undoAction);
+ ui->editToolBar->insertAction(ui->editToolBar->actions().front(),
+ m_redoAction);
+ ui->editToolBar->insertAction(m_redoAction, m_undoAction);
+ connect(m_undoStack, &QUndoStack::cleanChanged, ui->actionSave,
+ &QAction::setDisabled);
+ connect(m_undoStack, &QUndoStack::cleanChanged,
+ [this](bool clean) { setWindowModified(!clean); });
+
+ // Search/filter bar shortcuts.
+ auto *searchAction = new QAction(this);
+ searchAction->setShortcuts({QKeySequence::Find, Qt::Key_Slash});
+ m_searchBar->addAction(searchAction);
+ connect(searchAction, &QAction::triggered,
+ [this] {
+ if (m_searchBar->isHidden())
+ return;
+ m_searchBar->setFocus();
+ m_searchBar->selectAll();
+ });
+}
+
+void MainWindow::createNewModel()
+{
+ if (isWindowModified()) {
+ QMessageBox::StandardButton answer = QMessageBox::question(
+ this, tr("Save Model?"),
+ tr("Save changes to model '%1' before closing?")
+ .arg(QString::fromStdString(m_model->name())),
+ QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
+ QMessageBox::Save);
+
+ if (answer == QMessageBox::Cancel)
+ return;
+ if (answer == QMessageBox::Save) {
+ saveModel();
+ if (isWindowModified())
+ return;
+ }
+ }
+
+ m_inputFiles.clear();
+ m_model = std::make_shared<mef::Model>();
+
+ emit configChanged();
+}
+
+void MainWindow::openFiles(QString directory)
+{
+ QStringList filenames = QFileDialog::getOpenFileNames(
+ this, tr("Open Model Files"), directory,
+ QString::fromLatin1("%1 (*.mef *.opsa *.opsa-mef *.xml);;%2 (*.*)")
+ .arg(tr("Model Exchange Format"), tr("All files")));
+ if (filenames.empty())
+ return;
+ std::vector<std::string> inputFiles;
+ for (const auto &filename : filenames)
+ inputFiles.push_back(filename.toStdString());
+ addInputFiles(inputFiles);
+}
+
+void MainWindow::saveModel()
+{
+ if (m_inputFiles.empty() || m_inputFiles.size() > 1)
+ return saveModelAs();
+ saveToFile(m_inputFiles.front());
+}
+
+void MainWindow::saveModelAs()
+{
+ QString filename = QFileDialog::getSaveFileName(
+ this, tr("Save Model As"), QDir::homePath(),
+ QString::fromLatin1("%1 (*.mef *.opsa *.opsa-mef *.xml);;%2 (*.*)")
+ .arg(tr("Model Exchange Format"), tr("All files")));
+ if (filename.isNull())
+ return;
+ saveToFile(filename.toStdString());
+}
+
+void MainWindow::saveToFile(std::string destination)
+{
+ GUI_ASSERT(!destination.empty(), );
+ GUI_ASSERT(m_model, );
+ try {
+ mef::Serialize(*m_model, destination);
+ } catch (Error &err) {
+ QMessageBox::critical(this, tr("Save Error"),
+ QString::fromUtf8(err.what()));
+ return;
+ }
+ m_undoStack->setClean();
+ m_inputFiles.clear();
+ m_inputFiles.push_back(std::move(destination));
+}
+
+void MainWindow::closeEvent(QCloseEvent *event)
+{
+ if (!isWindowModified())
+ return event->accept();
+
+ QMessageBox::StandardButton answer = QMessageBox::question(
+ this, tr("Save Model?"),
+ tr("Save changes to model '%1' before closing?")
+ .arg(QString::fromStdString(m_model->name())),
+ QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
+ QMessageBox::Save);
+
+ if (answer == QMessageBox::Cancel)
+ return event->ignore();
+ if (answer == QMessageBox::Discard)
+ return event->accept();
+
+ saveModel();
+ return isWindowModified() ? event->ignore() : event->accept();
+}
+
+void MainWindow::exportReportAs()
+{
+ GUI_ASSERT(m_analysis, );
+ QString filename = QFileDialog::getSaveFileName(
+ this, tr("Export Report As"), QDir::homePath(),
+ QString::fromLatin1("%1 (*.mef *.opsa *.opsa-mef *.xml);;%2 (*.*)")
+ .arg(tr("Model Exchange Format"), tr("All files")));
+ if (filename.isNull())
+ return;
+ try {
+ Reporter().Report(*m_analysis, filename.toStdString());
+ } catch (Error &err) {
+ QMessageBox::critical(this, tr("Reporting Error"),
+ QString::fromUtf8(err.what()));
+ }
+}
+
+void MainWindow::setupZoomableView(ZoomableView *view)
+{
+ struct ZoomFilter : public QObject {
+ ZoomFilter(ZoomableView *zoomable, MainWindow *window)
+ : QObject(zoomable), m_window(window), m_zoomable(zoomable)
+ {
+ }
+ bool eventFilter(QObject *object, QEvent *event) override
+ {
+ auto setEnabled = [this] (bool state) {
+ m_window->m_zoomBox->setEnabled(state);
+ m_window->ui->actionZoomIn->setEnabled(state);
+ m_window->ui->actionZoomIn->setEnabled(state);
+ m_window->ui->actionZoomOut->setEnabled(state);
+ m_window->ui->actionBestFit->setEnabled(state);
+ m_window->ui->menuZoom->setEnabled(state);
+ };
+
+ if (event->type() == QEvent::Show) {
+ setEnabled(true);
+ m_window->m_zoomBox->setCurrentText(
+ QString::fromLatin1("%1%").arg(m_zoomable->getZoom()));
+
+ connect(m_zoomable, &ZoomableView::zoomChanged,
+ m_window->m_zoomBox, [this](int level) {
+ m_window->m_zoomBox->setCurrentText(
+ QString::fromLatin1("%1%").arg(level));
+ });
+ connect(m_window->m_zoomBox, &QComboBox::currentTextChanged,
+ m_zoomable, [this](QString text) {
+ text.remove(QLatin1Char('%'));
+ m_zoomable->setZoom(text.toInt());
+ });
+ connect(m_window->ui->actionZoomIn, &QAction::triggered,
+ m_zoomable, [this] { m_zoomable->zoomIn(5); });
+ connect(m_window->ui->actionZoomOut, &QAction::triggered,
+ m_zoomable, [this] { m_zoomable->zoomOut(5); });
+ connect(m_window->ui->actionBestFit, &QAction::triggered,
+ m_zoomable, &ZoomableView::zoomBestFit);
+ } else if (event->type() == QEvent::Hide) {
+ setEnabled(false);
+ disconnect(m_zoomable, 0, m_window->m_zoomBox, 0);
+ disconnect(m_window->m_zoomBox, 0, m_zoomable, 0);
+ disconnect(m_window->ui->actionZoomIn, 0, m_zoomable, 0);
+ disconnect(m_window->ui->actionZoomOut, 0, m_zoomable, 0);
+ disconnect(m_window->ui->actionBestFit, 0, m_zoomable, 0);
+ }
+ return QObject::eventFilter(object, event);
+ }
+ MainWindow *m_window;
+ ZoomableView *m_zoomable;
+ };
+ view->installEventFilter(new ZoomFilter(view, this));
+}
+
+template <class T>
+void MainWindow::setupPrintableView(T *view)
+{
+ static_assert(std::is_base_of<QObject, T>::value, "Missing QObject");
+ struct PrintFilter : public QObject {
+ PrintFilter(T *printable, MainWindow *window)
+ : QObject(printable), m_window(window), m_printable(printable)
+ {
+ }
+ bool eventFilter(QObject *object, QEvent *event) override
+ {
+ auto setEnabled = [this] (bool state) {
+ m_window->ui->actionPrint->setEnabled(state);
+ m_window->ui->actionPrintPreview->setEnabled(state);
+ };
+ if (event->type() == QEvent::Show) {
+ setEnabled(true);
+ connect(m_window->ui->actionPrint, &QAction::triggered,
+ m_printable, [this] { m_printable->print(); });
+ connect(m_window->ui->actionPrintPreview, &QAction::triggered,
+ m_printable, [this] { m_printable->printPreview(); });
+ } else if (event->type() == QEvent::Hide) {
+ setEnabled(false);
+ disconnect(m_window->ui->actionPrint, 0, m_printable, 0);
+ disconnect(m_window->ui->actionPrintPreview, 0, m_printable, 0);
+ }
+ return QObject::eventFilter(object, event);
+ }
+ MainWindow *m_window;
+ T *m_printable;
+ };
- auto *scene = new QGraphicsScene;
- ui->diagrams->setScene(scene);
+ view->installEventFilter(new PrintFilter(view, this));
}
-MainWindow::~MainWindow() { delete ui; }
+template <class T>
+void MainWindow::setupExportableView(T *view)
+{
+ struct ExportFilter : public QObject {
+ ExportFilter(T *exportable, MainWindow *window)
+ : QObject(exportable), m_window(window), m_exportable(exportable)
+ {
+ }
+
+ bool eventFilter(QObject *object, QEvent *event) override
+ {
+ if (event->type() == QEvent::Show) {
+ m_window->ui->actionExportAs->setEnabled(true);
+ connect(m_window->ui->actionExportAs, &QAction::triggered,
+ m_exportable, [this] { m_exportable->exportAs(); });
+ } else if (event->type() == QEvent::Hide) {
+ m_window->ui->actionExportAs->setEnabled(false);
+ disconnect(m_window->ui->actionExportAs, 0, m_exportable, 0);
+ }
+
+ return QObject::eventFilter(object, event);
+ }
+
+ MainWindow *m_window;
+ T *m_exportable;
+ };
+ view->installEventFilter(new ExportFilter(view, this));
+}
+
+template <class T>
+void MainWindow::setupSearchable(QObject *view, T *model)
+{
+ struct SearchFilter : public QObject {
+ SearchFilter(T *searchable, MainWindow *window)
+ : QObject(searchable), m_window(window), m_searchable(searchable)
+ {
+ }
+
+ bool eventFilter(QObject *object, QEvent *event) override
+ {
+ if (event->type() == QEvent::Show) {
+ m_window->m_searchBar->setHidden(false);
+ m_window->m_searchBar->setText(
+ m_searchable->filterRegExp().pattern());
+ connect(m_window->m_searchBar, &QLineEdit::editingFinished,
+ object, [this] {
+ m_searchable->setFilterRegExp(
+ m_window->m_searchBar->text());
+ });
+ } else if (event->type() == QEvent::Hide) {
+ m_window->m_searchBar->setHidden(true);
+ disconnect(m_window->m_searchBar, 0, object, 0);
+ }
+
+ return QObject::eventFilter(object, event);
+ }
+
+ MainWindow *m_window;
+ T *m_searchable;
+ };
+ view->installEventFilter(new SearchFilter(model, this));
+}
+
+template <>
+mef::FaultTree *MainWindow::getFaultTree(mef::Gate *gate)
+{
+ /// @todo Duplicate code from EventDialog.
+ auto it = boost::find_if(
+ m_model->fault_trees(), [&gate](const mef::FaultTreePtr &faultTree) {
+ return faultTree->gates().count(gate->name());
+ });
+ GUI_ASSERT(it != m_model->fault_trees().end(), nullptr);
+ return it->get();
+}
+
+template <class T>
+void MainWindow::removeEvent(T *event, mef::FaultTree *faultTree)
+{
+ m_undoStack->push(
+ new model::Model::RemoveEvent<T>(event, m_guiModel.get(), faultTree));
+}
+
+template <>
+void MainWindow::removeEvent(model::Gate *event, mef::FaultTree *faultTree)
+{
+ GUI_ASSERT(faultTree->top_events().empty() == false, );
+ GUI_ASSERT(faultTree->gates().empty() == false, );
+ if (faultTree->top_events().front() != event->data()) {
+ m_undoStack->push(new model::Model::RemoveEvent<model::Gate>(
+ event, m_guiModel.get(), faultTree));
+ return;
+ }
+ QString faultTreeName = QString::fromStdString(faultTree->name());
+ if (faultTree->gates().size() > 1) {
+ QMessageBox::information(
+ this, tr("Dependency Container Removal"),
+ tr("Fault tree '%1' with root '%2' is not removable because"
+ " it has dependent non-root gates."
+ " Remove the gates from the fault tree"
+ " before this operation.")
+ .arg(faultTreeName, event->id()));
+ return;
+ }
+ m_undoStack->beginMacro(tr("Remove fault tree '%1' with root '%2'")
+ .arg(faultTreeName, event->id()));
+ m_undoStack->push(new model::Model::RemoveEvent<model::Gate>(
+ event, m_guiModel.get(), faultTree));
+ m_undoStack->push(
+ new model::Model::RemoveFaultTree(faultTree, m_guiModel.get()));
+ m_undoStack->endMacro();
+}
+
+template <class T>
+void MainWindow::setupRemovable(QAbstractItemView *view)
+{
+ struct RemoveFilter : public QObject {
+ RemoveFilter(QAbstractItemView *removable, MainWindow *window)
+ : QObject(removable), m_window(window), m_removable(removable) {}
+
+ void react(const QModelIndexList &indexes)
+ {
+ m_window->ui->actionRemoveElement->setEnabled(
+ !(indexes.empty() || indexes.front().parent().isValid()));
+ }
+
+ bool eventFilter(QObject *object, QEvent *event) override
+ {
+ if (event->type() == QEvent::Show) {
+ react(m_removable->selectionModel()->selectedIndexes());
+ connect(m_removable->selectionModel(),
+ &QItemSelectionModel::selectionChanged,
+ m_window->ui->actionRemoveElement,
+ [this](const QItemSelection &selected) {
+ react(selected.indexes());
+ });
+ connect(
+ m_window->ui->actionRemoveElement, &QAction::triggered,
+ m_removable, [this] {
+ auto currentIndexes
+ = m_removable->selectionModel()->selectedIndexes();
+ GUI_ASSERT(currentIndexes.empty() == false, );
+ auto index = currentIndexes.front();
+ GUI_ASSERT(index.parent().isValid() == false, );
+ auto *proxyModel = static_cast<QSortFilterProxyModel *>(
+ m_removable->model());
+ auto *element = static_cast<T *>(
+ proxyModel->mapToSource(index).internalPointer());
+ GUI_ASSERT(element, );
+ auto parents
+ = m_window->m_guiModel->parents(element->data());
+ if (!parents.empty()) {
+ QMessageBox::information(
+ m_window, tr("Dependency Event Removal"),
+ tr("Event '%1' is not removable because"
+ " it has dependents."
+ " Remove the event from the dependents"
+ " before this operation.")
+ .arg(element->id()));
+ return;
+ }
+ m_window->removeEvent(
+ element,
+ m_window->getFaultTree(element->data()));
+ });
+ } else if (event->type() == QEvent::Hide) {
+ m_window->ui->actionRemoveElement->setEnabled(false);
+ disconnect(m_window->ui->actionRemoveElement, 0, m_removable,
+ 0);
+ }
+
+ return QObject::eventFilter(object, event);
+ }
+
+ MainWindow *m_window;
+ QAbstractItemView *m_removable;
+ };
+ view->installEventFilter(new RemoveFilter(view, this));
+}
+
+template <>
+mef::FormulaPtr MainWindow::extract(const EventDialog &dialog)
+{
+ auto formula = std::make_unique<mef::Formula>(dialog.connective());
+ if (formula->type() == mef::kVote)
+ formula->vote_number(dialog.voteNumber());
+
+ for (const std::string &arg : dialog.arguments()) {
+ try {
+ formula->AddArgument(m_model->GetEvent(arg));
+ } catch (UndefinedElement &) {
+ auto argEvent = std::make_unique<mef::BasicEvent>(arg);
+ argEvent->AddAttribute({"flavor", "undeveloped", ""});
+ formula->AddArgument(argEvent.get());
+ /// @todo Add into the parent undo.
+ m_undoStack->push(new model::Model::AddEvent<model::BasicEvent>(
+ std::move(argEvent), m_guiModel.get()));
+ }
+ }
+ return formula;
+}
+
+template <>
+mef::BasicEventPtr MainWindow::extract(const EventDialog &dialog)
+{
+ auto basicEvent
+ = std::make_unique<mef::BasicEvent>(dialog.name().toStdString());
+ basicEvent->label(dialog.label().toStdString());
+ switch (dialog.currentType()) {
+ case EventDialog::BasicEvent:
+ break;
+ case EventDialog::Undeveloped:
+ basicEvent->AddAttribute({"flavor", "undeveloped", ""});
+ break;
+ case EventDialog::Conditional:
+ basicEvent->AddAttribute({"flavor", "conditional", ""});
+ break;
+ default:
+ GUI_ASSERT(false && "unexpected event type", nullptr);
+ }
+ if (auto p_expression = dialog.expression()) {
+ basicEvent->expression(p_expression.get());
+ m_model->Add(std::move(p_expression));
+ }
+ return basicEvent;
+}
+
+template <>
+mef::HouseEventPtr MainWindow::extract(const EventDialog &dialog)
+{
+ GUI_ASSERT(dialog.currentType() == EventDialog::HouseEvent, nullptr);
+ auto houseEvent
+ = std::make_unique<mef::HouseEvent>(dialog.name().toStdString());
+ houseEvent->label(dialog.label().toStdString());
+ houseEvent->state(dialog.booleanConstant());
+ return houseEvent;
+}
+
+template <>
+mef::GatePtr MainWindow::extract(const EventDialog &dialog)
+{
+ GUI_ASSERT(dialog.currentType() == EventDialog::Gate, nullptr);
+ auto gate = std::make_unique<mef::Gate>(dialog.name().toStdString());
+ gate->label(dialog.label().toStdString());
+ gate->formula(extract<mef::Formula>(dialog));
+ return gate;
+}
+
+void MainWindow::addElement()
+{
+ EventDialog dialog(m_model.get(), this);
+ if (dialog.exec() == QDialog::Rejected)
+ return;
+ switch (dialog.currentType()) {
+ case EventDialog::HouseEvent:
+ m_undoStack->push(new model::Model::AddEvent<model::HouseEvent>(
+ extract<mef::HouseEvent>(dialog), m_guiModel.get()));
+ break;
+ case EventDialog::BasicEvent:
+ case EventDialog::Undeveloped:
+ case EventDialog::Conditional:
+ m_undoStack->push(new model::Model::AddEvent<model::BasicEvent>(
+ extract<mef::BasicEvent>(dialog), m_guiModel.get()));
+ break;
+ case EventDialog::Gate: {
+ m_undoStack->beginMacro(
+ tr("Add fault tree '%1' with gate '%2'")
+ .arg(QString::fromStdString(dialog.faultTree()),
+ dialog.name()));
+ auto faultTree = std::make_unique<mef::FaultTree>(dialog.faultTree());
+ auto *faultTreeAddress = faultTree.get();
+ m_undoStack->push(new model::Model::AddFaultTree(std::move(faultTree),
+ m_guiModel.get()));
+ m_undoStack->push(new model::Model::AddEvent<model::Gate>(
+ extract<mef::Gate>(dialog), m_guiModel.get(), faultTreeAddress));
+ faultTreeAddress->CollectTopEvents();
+ m_undoStack->endMacro();
+ } break;
+ default:
+ GUI_ASSERT(false && "unexpected event type", );
+ }
+}
+
+mef::FaultTree *MainWindow::getFaultTree(const EventDialog &dialog)
+{
+ if (dialog.faultTree().empty())
+ return nullptr;
+ auto it = m_model->fault_trees().find(dialog.faultTree());
+ GUI_ASSERT(it != m_model->fault_trees().end(), nullptr);
+ return it->get();
+}
+
+template <class T>
+void MainWindow::editElement(EventDialog *dialog, model::Element *element)
+{
+ if (dialog->name() != element->id()) {
+ m_undoStack->push(new model::Element::SetId<T>(
+ static_cast<T *>(element), dialog->name(), m_model.get(),
+ getFaultTree(*dialog)));
+ }
+ if (dialog->label() != element->label())
+ m_undoStack->push(
+ new model::Element::SetLabel(element, dialog->label()));
+}
+
+void MainWindow::editElement(EventDialog *dialog, model::BasicEvent *element)
+{
+ editElement<model::BasicEvent>(dialog, element);
+ switch (dialog->currentType()) {
+ case EventDialog::HouseEvent:
+ m_undoStack->push(new model::Model::ChangeEventType<model::BasicEvent,
+ model::HouseEvent>(
+ element, extract<mef::HouseEvent>(*dialog), m_guiModel.get(),
+ getFaultTree(*dialog)));
+ return;
+ case EventDialog::BasicEvent:
+ case EventDialog::Undeveloped:
+ case EventDialog::Conditional:
+ break;
+ case EventDialog::Gate:
+ m_undoStack->push(
+ new model::Model::ChangeEventType<model::BasicEvent, model::Gate>(
+ element, extract<mef::Gate>(*dialog), m_guiModel.get(),
+ getFaultTree(*dialog)));
+ return;
+ default:
+ GUI_ASSERT(false && "Unexpected event type", );
+ }
+ std::unique_ptr<mef::Expression> expression = dialog->expression();
+ auto isEqual = [](mef::Expression *lhs, mef::Expression *rhs) {
+ if (lhs == rhs) // Assumes immutable expressions.
+ return true;
+ if (!lhs || !rhs)
+ return false;
+
+ auto *const_lhs = dynamic_cast<mef::ConstantExpression *>(lhs);
+ auto *const_rhs = dynamic_cast<mef::ConstantExpression *>(rhs);
+ if (const_lhs && const_rhs && const_lhs->value() == const_rhs->value())
+ return true;
+
+ auto *exp_lhs = dynamic_cast<mef::Exponential *>(lhs);
+ auto *exp_rhs = dynamic_cast<mef::Exponential *>(rhs);
+ if (exp_lhs && exp_rhs
+ && exp_lhs->args().front()->value()
+ == exp_rhs->args().front()->value())
+ return true;
+
+ return false;
+ };
+
+ if (!isEqual(expression.get(), element->expression())) {
+ m_undoStack->push(
+ new model::BasicEvent::SetExpression(element, expression.get()));
+ m_model->Add(std::move(expression));
+ }
+
+ auto flavorToType = [](model::BasicEvent::Flavor flavor) {
+ switch (flavor) {
+ case model::BasicEvent::Basic:
+ return EventDialog::BasicEvent;
+ case model::BasicEvent::Undeveloped:
+ return EventDialog::Undeveloped;
+ case model::BasicEvent::Conditional:
+ return EventDialog::Conditional;
+ }
+ assert(false);
+ };
+
+ if (dialog->currentType() != flavorToType(element->flavor())) {
+ m_undoStack->push([&dialog, &element]() -> QUndoCommand * {
+ switch (dialog->currentType()) {
+ case EventDialog::BasicEvent:
+ return new model::BasicEvent::SetFlavor(
+ element, model::BasicEvent::Basic);
+ case EventDialog::Undeveloped:
+ return new model::BasicEvent::SetFlavor(
+ element, model::BasicEvent::Undeveloped);
+ case EventDialog::Conditional:
+ return new model::BasicEvent::SetFlavor(
+ element, model::BasicEvent::Conditional);
+ default:
+ GUI_ASSERT(false && "Unexpected event type", nullptr);
+ }
+ }());
+ }
+}
+
+void MainWindow::editElement(EventDialog *dialog, model::HouseEvent *element)
+{
+ editElement<model::HouseEvent>(dialog, element);
+ switch (dialog->currentType()) {
+ case EventDialog::HouseEvent:
+ break;
+ case EventDialog::BasicEvent:
+ case EventDialog::Undeveloped:
+ case EventDialog::Conditional:
+ m_undoStack->push(new model::Model::ChangeEventType<model::HouseEvent,
+ model::BasicEvent>(
+ element, extract<mef::BasicEvent>(*dialog), m_guiModel.get(),
+ getFaultTree(*dialog)));
+ return;
+ case EventDialog::Gate:
+ m_undoStack->push(
+ new model::Model::ChangeEventType<model::HouseEvent, model::Gate>(
+ element, extract<mef::Gate>(*dialog), m_guiModel.get(),
+ getFaultTree(*dialog)));
+ return;
+ default:
+ GUI_ASSERT(false && "Unexpected event type", );
+ }
+ if (dialog->booleanConstant() != element->state())
+ m_undoStack->push(new model::HouseEvent::SetState(
+ element, dialog->booleanConstant()));
+}
+
+void MainWindow::editElement(EventDialog *dialog, model::Gate *element)
+{
+ editElement<model::Gate>(dialog, element);
+ switch (dialog->currentType()) {
+ case EventDialog::HouseEvent:
+ m_undoStack->push(
+ new model::Model::ChangeEventType<model::Gate, model::HouseEvent>(
+ element, extract<mef::HouseEvent>(*dialog), m_guiModel.get(),
+ getFaultTree(*dialog)));
+ return;
+ case EventDialog::BasicEvent:
+ case EventDialog::Undeveloped:
+ case EventDialog::Conditional:
+ m_undoStack->push(
+ new model::Model::ChangeEventType<model::Gate, model::BasicEvent>(
+ element, extract<mef::BasicEvent>(*dialog), m_guiModel.get(),
+ getFaultTree(*dialog)));
+ return;
+ case EventDialog::Gate:
+ break;
+ default:
+ GUI_ASSERT(false && "Unexpected event type", );
+ }
+
+ bool formulaChanged = [&dialog, &element] {
+ if (dialog->connective() != element->type())
+ return true;
+ if (element->type() == mef::kVote
+ && dialog->voteNumber() != element->voteNumber())
+ return true;
+ std::vector<std::string> dialogArgs = dialog->arguments();
+ if (element->numArgs() != dialogArgs.size())
+ return true;
+ auto it = dialogArgs.begin();
+ for (const mef::Formula::EventArg &arg : element->args()) {
+ if (*it != ext::as<const mef::Event *>(arg)->id())
+ return true;
+ ++it;
+ }
+ return false;
+ }();
+ if (formulaChanged)
+ m_undoStack->push(new model::Gate::SetFormula(
+ element, extract<mef::Formula>(*dialog)));
+}
+
+template <class ContainerModel>
+QAbstractItemView *MainWindow::constructElementTable(model::Model *guiModel,
+ QWidget *parent)
+{
+ auto *table = new QTableView(parent);
+ auto *tableModel = new ContainerModel(guiModel, table);
+ auto *proxyModel = new model::SortFilterProxyModel(table);
+ proxyModel->setSourceModel(tableModel);
+ table->setModel(proxyModel);
+ table->setSelectionBehavior(QAbstractItemView::SelectRows);
+ table->setSelectionMode(QAbstractItemView::SingleSelection);
+ table->setWordWrap(false);
+ table->resizeColumnsToContents();
+ table->setSortingEnabled(true);
+ setupSearchable(table, proxyModel);
+ setupRemovable<typename ContainerModel::ItemModel>(table);
+ connect(table, &QAbstractItemView::activated,
+ [this, proxyModel](const QModelIndex &index) {
+ GUI_ASSERT(index.isValid(), );
+ EventDialog dialog(m_model.get(), this);
+ auto *item = static_cast<typename ContainerModel::ItemModel *>(
+ proxyModel->mapToSource(index).internalPointer());
+ dialog.setupData(*item);
+ if (dialog.exec() == QDialog::Accepted)
+ editElement(&dialog, item);
+ });
+ return table;
+}
+
+/// Specialization to show gates as trees in tables.
+template <>
+QAbstractItemView *MainWindow::constructElementTable<model::GateContainerModel>(
+ model::Model *guiModel, QWidget *parent)
+{
+ auto *tree = new QTreeView(parent);
+ auto *tableModel = new model::GateContainerModel(guiModel, tree);
+ auto *proxyModel = new model::SortFilterProxyModel(tree);
+ proxyModel->setSourceModel(tableModel);
+ tree->setModel(proxyModel);
+ tree->setSelectionBehavior(QAbstractItemView::SelectRows);
+ tree->setSelectionMode(QAbstractItemView::SingleSelection);
+ tree->setWordWrap(false);
+ tree->resizeColumnToContents(0);
+ tree->setColumnWidth(0, 2 * tree->columnWidth(0));
+ tree->setAlternatingRowColors(true);
+ tree->setSortingEnabled(true);
+
+ setupSearchable(tree, proxyModel);
+ setupRemovable<model::Gate>(tree);
+ connect(tree, &QAbstractItemView::activated,
+ [this, proxyModel](const QModelIndex &index) {
+ GUI_ASSERT(index.isValid(), );
+ if (index.parent().isValid())
+ return;
+ EventDialog dialog(m_model.get(), this);
+ auto *item = static_cast<model::Gate *>(
+ proxyModel->mapToSource(index).internalPointer());
+ dialog.setupData(*item);
+ if (dialog.exec() == QDialog::Accepted)
+ editElement(&dialog, item);
+ });
+ return tree;
+}
+
+void MainWindow::resetModelTree()
+{
+ while (ui->tabWidget->count()) {
+ auto *widget = ui->tabWidget->widget(0);
+ ui->tabWidget->removeTab(0);
+ delete widget;
+ }
+ m_guiModel = std::make_unique<model::Model>(m_model.get());
+ auto *oldModel = ui->modelTree->model();
+ ui->modelTree->setModel(new ModelTree(m_guiModel.get(), this));
+ delete oldModel;
+
+ connect(m_guiModel.get(), &model::Model::modelNameChanged, this, [this] {
+ setWindowTitle(QString::fromLatin1("%1[*]").arg(
+ QString::fromStdString(m_model->name())));
+ });
+}
+
+void MainWindow::activateModelTree(const QModelIndex &index)
+{
+ GUI_ASSERT(index.isValid(), );
+ if (index.parent().isValid() == false) {
+ switch (static_cast<ModelTree::Row>(index.row())) {
+ case ModelTree::Row::Gates: {
+ auto *table = constructElementTable<model::GateContainerModel>(
+ m_guiModel.get(), this);
+ ui->tabWidget->addTab(table, tr("Gates"));
+ ui->tabWidget->setCurrentWidget(table);
+ return;
+ }
+ case ModelTree::Row::BasicEvents: {
+ auto *table
+ = constructElementTable<model::BasicEventContainerModel>(
+ m_guiModel.get(), this);
+ ui->tabWidget->addTab(table, tr("Basic Events"));
+ ui->tabWidget->setCurrentWidget(table);
+ return;
+ }
+ case ModelTree::Row::HouseEvents: {
+ auto *table
+ = constructElementTable<model::HouseEventContainerModel>(
+ m_guiModel.get(), this);
+ ui->tabWidget->addTab(table, tr("House Events"));
+ ui->tabWidget->setCurrentWidget(table);
+ return;
+ }
+ case ModelTree::Row::FaultTrees:
+ return;
+ }
+ GUI_ASSERT(false, );
+ }
+ GUI_ASSERT(index.parent().parent().isValid() == false, );
+ GUI_ASSERT(index.parent().row()
+ == static_cast<int>(ModelTree::Row::FaultTrees), );
+ auto faultTree = static_cast<mef::FaultTree *>(index.internalPointer());
+ GUI_ASSERT(faultTree, );
+ activateFaultTreeDiagram(faultTree);
+}
+
+void MainWindow::activateFaultTreeDiagram(mef::FaultTree *faultTree)
+{
+ GUI_ASSERT(faultTree, );
+ GUI_ASSERT(faultTree->top_events().size() == 1, );
+ auto *topGate = faultTree->top_events().front();
+ auto *view = new DiagramView(this);
+ auto *scene = new diagram::DiagramScene(
+ m_guiModel->gates().find(topGate)->get(), m_guiModel.get(), view);
+ view->setScene(scene);
+ view->setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
+ view->setRenderHints(QPainter::Antialiasing
+ | QPainter::SmoothPixmapTransform);
+ view->setAlignment(Qt::AlignTop);
+ view->ensureVisible(0, 0, 0, 0);
+ setupZoomableView(view);
+ setupPrintableView(view);
+ setupExportableView(view);
+ ui->tabWidget->addTab(
+ view,
+ tr("Fault Tree: %1").arg(QString::fromStdString(faultTree->name())));
+ ui->tabWidget->setCurrentWidget(view);
+
+ connect(scene, &diagram::DiagramScene::activated, this,
+ [this](model::Element *element) {
+ EventDialog dialog(m_model.get(), this);
+ auto action = [this, &dialog](auto *target) {
+ dialog.setupData(*target);
+ if (dialog.exec() == QDialog::Accepted) {
+ editElement(&dialog, target);
+ }
+ };
+ /// @todo Redesign/remove the RAII!
+ if (auto *basic = dynamic_cast<model::BasicEvent *>(element)) {
+ action(basic);
+ } else if (auto *gate = dynamic_cast<model::Gate *>(element)) {
+ action(gate);
+ } else {
+ auto *house = dynamic_cast<model::HouseEvent *>(element);
+ GUI_ASSERT(house, );
+ action(house);
+ }
+ });
+}
+
+void MainWindow::resetReportWidget(std::unique_ptr<core::RiskAnalysis> analysis)
+{
+ ui->reportTreeWidget->clear();
+ m_reportActions.clear();
+ m_analysis = std::move(analysis);
+ ui->actionExportReportAs->setEnabled(static_cast<bool>(m_analysis));
+ if (!m_analysis)
+ return;
+
+ struct {
+ QString operator()(const mef::Gate *gate)
+ {
+ return QString::fromStdString(gate->id());
+ }
+
+ QString operator()(const std::pair<const mef::InitiatingEvent &,
+ const mef::Sequence &> &)
+ {
+ GUI_ASSERT(false && "unexpected analysis target", {});
+ return {};
+ }
+ } nameExtractor;
+ for (const core::RiskAnalysis::Result &result : m_analysis->results()) {
+ QString name = boost::apply_visitor(nameExtractor, result.id);
+ auto *widgetItem = new QTreeWidgetItem({name});
+ ui->reportTreeWidget->addTopLevelItem(widgetItem);
+
+ GUI_ASSERT(result.fault_tree_analysis,);
+ auto *productItem = new QTreeWidgetItem(
+ {tr("Products: %L1")
+ .arg(result.fault_tree_analysis->products().size())});
+ widgetItem->addChild(productItem);
+ m_reportActions.emplace(productItem, [this, &result, name] {
+ auto *table = new QTableWidget(nullptr);
+ const auto &products = result.fault_tree_analysis->products();
+ double sum = 0;
+ if (result.probability_analysis) {
+ table->setColumnCount(4);
+ table->setHorizontalHeaderLabels({tr("Product"), tr("Order"),
+ tr("Probability"),
+ tr("Contribution")});
+ for (const core::Product& product : products)
+ sum += product.p();
+ } else {
+ table->setColumnCount(2);
+ table->setHorizontalHeaderLabels({tr("Product"), tr("Order")});
+ }
+ table->setRowCount(products.size());
+ int row = 0;
+ for (const core::Product &product : products) {
+ QStringList members;
+ for (const core::Literal &literal : product) {
+ members.push_back(QString::fromStdString(
+ (literal.complement ? "\u00AC" : "")
+ + literal.event.id()));
+ }
+ table->setItem(row, 0, constructTableItem(members.join(
+ QStringLiteral(" \u22C5 "))));
+ table->setItem(row, 1, constructTableItem(product.order()));
+ if (result.probability_analysis) {
+ table->setItem(row, 2, constructTableItem(product.p()));
+ table->setItem(row, 3,
+ constructTableItem(product.p() / sum));
+ }
+ ++row;
+ }
+ table->setWordWrap(false);
+ table->resizeColumnsToContents();
+ table->setSortingEnabled(true);
+ ui->tabWidget->addTab(table, tr("Products: %1").arg(name));
+ ui->tabWidget->setCurrentWidget(table);
+ });
+
+ if (result.probability_analysis) {
+ widgetItem->addChild(new QTreeWidgetItem(
+ {tr("Probability: %1")
+ .arg(result.probability_analysis->p_total())}));
+ }
+
+ if (result.importance_analysis) {
+ auto *importanceItem = new QTreeWidgetItem(
+ {tr("Importance Factors: %L1")
+ .arg(result.importance_analysis->importance().size())});
+ widgetItem->addChild(importanceItem);
+ m_reportActions.emplace(importanceItem, [this, &result, name] {
+ auto *table = new QTableWidget(nullptr);
+ table->setColumnCount(8);
+ table->setHorizontalHeaderLabels(
+ {tr("ID"), tr("Occurrence"), tr("Probability"), tr("MIF"),
+ tr("CIF"), tr("DIF"), tr("RAW"), tr("RRW")});
+ auto &records = result.importance_analysis->importance();
+ table->setRowCount(records.size());
+ int row = 0;
+ for (const core::ImportanceRecord &record : records) {
+ table->setItem(
+ row, 0, constructTableItem(
+ QString::fromStdString(record.event.id())));
+ table->setItem(
+ row, 1, constructTableItem(record.factors.occurrence));
+ table->setItem(row, 2,
+ constructTableItem(record.event.p()));
+ table->setItem(row, 3,
+ constructTableItem(record.factors.mif));
+ table->setItem(row, 4,
+ constructTableItem(record.factors.cif));
+ table->setItem(row, 5,
+ constructTableItem(record.factors.dif));
+ table->setItem(row, 6,
+ constructTableItem(record.factors.raw));
+ table->setItem(row, 7,
+ constructTableItem(record.factors.rrw));
+ ++row;
+ }
+
+ table->setWordWrap(false);
+ table->resizeColumnsToContents();
+ table->setSortingEnabled(true);
+ ui->tabWidget->addTab(table, tr("Importance: %1").arg(name));
+ ui->tabWidget->setCurrentWidget(table);
+ });
+ }
+ }
+}
} // namespace gui
} // namespace scram
diff --git a/gui/mainwindow.h b/gui/mainwindow.h
index f6872c8..2f1c0fe 100644
--- a/gui/mainwindow.h
+++ b/gui/mainwindow.h
@@ -18,10 +18,29 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
+#include <functional>
+#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
+#include <QAbstractItemView>
+#include <QAction>
+#include <QComboBox>
+#include <QDir>
+#include <QLineEdit>
#include <QMainWindow>
+#include <QRegularExpressionValidator>
+#include <QTreeWidgetItem>
+#include <QUndoStack>
+
+#include "src/model.h"
+#include "src/risk_analysis.h"
+#include "src/settings.h"
+
+#include "eventdialog.h"
+#include "model.h"
+#include "zoomableview.h"
namespace Ui {
class MainWindow;
@@ -38,15 +57,151 @@ public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
- void setConfig(const std::string &config)
- {
- m_config = QString::fromStdString(config);
- }
- void addInputFiles(const std::vector<std::string>& /*input_files*/) {}
+ void setConfig(const std::string &configPath,
+ std::vector<std::string> inputFiles = {});
+ void addInputFiles(const std::vector<std::string> &inputFiles);
+
+signals:
+ void configChanged();
+
+private slots:
+ /**
+ * @brief Opens a new project configuration.
+ *
+ * The current project and input files are reset.
+ */
+ void createNewModel();
+
+ /**
+ * @brief Opens model files.
+ */
+ void openFiles(QString directory = QDir::homePath());
+
+ /**
+ * @brief Saves the project to a file.
+ *
+ * If the project is new,
+ * it does not have a default destination file.
+ * The user is required to specify the file upon save.
+ */
+ void saveModel();
+
+ /**
+ * @brief Saves the project to a potentially different file.
+ */
+ void saveModelAs();
+
+ /**
+ * Exports the current analysis report.
+ */
+ void exportReportAs();
+
+ /// Handles element addition with a dialog.
+ void addElement();
private:
- Ui::MainWindow *ui;
- QString m_config; ///< The main project configuration file.
+ void setupStatusBar(); ///< Setup widgets in the status bar.
+ void setupActions(); ///< Setup all the actions with connections.
+
+ void setupZoomableView(ZoomableView *view); ///< Connect to actions.
+
+ /// Connects print actions.
+ ///
+ /// @tparam T Any type with print() and printPreview() functions.
+ template <class T>
+ void setupPrintableView(T *view);
+
+ /// Connects the export actions to a widget.
+ ///
+ /// @tparam Any type with exportAs() function.
+ template <class T>
+ void setupExportableView(T *view);
+
+ /// Connects the search bar to the model of the view.
+ ///
+ /// @tparam T Any model type with setFilterRegExp(QString).
+ template <class T>
+ void setupSearchable(QObject *view, T *model);
+
+ /// Sets up remove action activation upon selection.
+ /// Only selection of top indices activate the removal.
+ ///
+ /// @tparam T The type of the objects to remove.
+ ///
+ /// @pre Selections are single row.
+ /// @pre The model is proxy.
+ template <class T>
+ void setupRemovable(QAbstractItemView *view);
+
+ /// @tparam T The MEF type.
+ ///
+ /// @returns The fault tree container of the element.
+ template <class T>
+ mef::FaultTree *getFaultTree(T *) { return nullptr; }
+
+ /// @tparam T The model proxy type.
+ template <class T>
+ void removeEvent(T *event, mef::FaultTree *faultTree);
+
+ template <class ContainerModel>
+ QAbstractItemView *constructElementTable(model::Model *guiModel,
+ QWidget *parent);
+
+ /// @returns A new element constructed with the dialog data.
+ template <class T>
+ std::unique_ptr<T> extract(const EventDialog &dialog);
+
+ mef::FaultTree *getFaultTree(const EventDialog &dialog);
+
+ template <class T>
+ void editElement(EventDialog *dialog, model::Element *element);
+ void editElement(EventDialog *dialog, model::HouseEvent *element);
+ void editElement(EventDialog *dialog, model::BasicEvent *element);
+ void editElement(EventDialog *dialog, model::Gate *element);
+
+ /**
+ * Resets the tree view of the new model.
+ */
+ void resetModelTree();
+
+ /// Activates the model tree elements.
+ void activateModelTree(const QModelIndex &index);
+ /// Activates the fault tree view.
+ void activateFaultTreeDiagram(mef::FaultTree *faultTree);
+
+ /**
+ * @brief Resets the report view.
+ *
+ * @param analysis The analysis with results.
+ * nullptr to clear the report widget.
+ */
+ void resetReportWidget(std::unique_ptr<core::RiskAnalysis> analysis);
+
+ /**
+ * Saves the model and sets the model file.
+ *
+ * @param destination The destination file to become the main model file.
+ */
+ void saveToFile(std::string destination);
+
+ /// Override to save the model before closing the application.
+ void closeEvent(QCloseEvent *event) override;
+
+ std::unique_ptr<Ui::MainWindow> ui;
+ QAction *m_undoAction;
+ QAction *m_redoAction;
+ QUndoStack *m_undoStack;
+ QLineEdit *m_searchBar;
+
+ std::vector<std::string> m_inputFiles; ///< The project model files.
+ core::Settings m_settings; ///< The analysis settings.
+ std::shared_ptr<mef::Model> m_model; ///< The analysis model.
+ std::unique_ptr<model::Model> m_guiModel; ///< The GUI Model wrapper.
+ QRegularExpressionValidator m_percentValidator; ///< Zoom percent input.
+ QComboBox *m_zoomBox; ///< The main zoom chooser/displayer widget.
+ std::unique_ptr<core::RiskAnalysis> m_analysis; ///< Report container.
+ std::unordered_map<QTreeWidgetItem *, std::function<void()>>
+ m_reportActions; ///< Actions on elements of the report tree widget.
};
} // namespace gui
diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui
index 9909636..9a8e6f7 100644
--- a/gui/mainwindow.ui
+++ b/gui/mainwindow.ui
@@ -24,7 +24,7 @@
</property>
<property name="windowIcon">
<iconset resource="res.qrc">
- <normaloff>:/images/scram_logo.png</normaloff>:/images/scram_logo.png</iconset>
+ <normaloff>:/images/scram_solid128x128.png</normaloff>:/images/scram_solid128x128.png</iconset>
</property>
<property name="autoFillBackground">
<bool>false</bool>
@@ -47,7 +47,20 @@
<number>0</number>
</property>
<item row="0" column="0">
- <widget class="QGraphicsView" name="diagrams"/>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="styleSheet">
+ <string notr="true">QTabWidget::pane { border: 0; }</string>
+ </property>
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ <property name="tabsClosable">
+ <bool>true</bool>
+ </property>
+ <property name="movable">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
</layout>
</widget>
@@ -64,14 +77,796 @@
<property name="title">
<string>&Help</string>
</property>
+ <addaction name="actionAboutScram"/>
+ <addaction name="actionAboutQt"/>
</widget>
+ <widget class="QMenu" name="menuFile">
+ <property name="title">
+ <string>&File</string>
+ </property>
+ <addaction name="actionNewModel"/>
+ <addaction name="actionOpenFiles"/>
+ <addaction name="separator"/>
+ <addaction name="actionSave"/>
+ <addaction name="actionSaveAs"/>
+ <addaction name="separator"/>
+ <addaction name="actionExportAs"/>
+ <addaction name="actionExportReportAs"/>
+ <addaction name="separator"/>
+ <addaction name="actionPrintPreview"/>
+ <addaction name="actionPrint"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <widget class="QMenu" name="menuView">
+ <property name="title">
+ <string>&View</string>
+ </property>
+ <widget class="QMenu" name="menuZoom">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="title">
+ <string>&Zoom</string>
+ </property>
+ <addaction name="action400"/>
+ <addaction name="action200"/>
+ <addaction name="action150"/>
+ <addaction name="action125"/>
+ <addaction name="action100"/>
+ <addaction name="action85"/>
+ <addaction name="action70"/>
+ <addaction name="action50"/>
+ </widget>
+ <widget class="QMenu" name="menuToolbars">
+ <property name="title">
+ <string>&Toolbars</string>
+ </property>
+ <addaction name="actionModelToolBar"/>
+ <addaction name="actionEditToolBar"/>
+ <addaction name="actionZoomToolBar"/>
+ <addaction name="actionAnalysisToolBar"/>
+ </widget>
+ <addaction name="actionZoomIn"/>
+ <addaction name="actionZoomOut"/>
+ <addaction name="menuZoom"/>
+ <addaction name="actionBestFit"/>
+ <addaction name="separator"/>
+ <addaction name="menuToolbars"/>
+ <addaction name="separator"/>
+ <addaction name="actionData"/>
+ <addaction name="actionReports"/>
+ </widget>
+ <widget class="QMenu" name="menuAnalysis">
+ <property name="title">
+ <string>&Analysis</string>
+ </property>
+ <addaction name="actionSettings"/>
+ <addaction name="separator"/>
+ <addaction name="actionRun"/>
+ </widget>
+ <widget class="QMenu" name="menuEdit">
+ <property name="title">
+ <string>&Edit</string>
+ </property>
+ <addaction name="separator"/>
+ <addaction name="actionAddElement"/>
+ <addaction name="actionRemoveElement"/>
+ <addaction name="separator"/>
+ <addaction name="actionRenameModel"/>
+ </widget>
+ <addaction name="menuFile"/>
+ <addaction name="menuEdit"/>
+ <addaction name="menuView"/>
+ <addaction name="menuAnalysis"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
+ <widget class="QToolBar" name="modelToolBar">
+ <property name="windowTitle">
+ <string>Model Tool Bar</string>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionNewModel"/>
+ <addaction name="actionOpenFiles"/>
+ <addaction name="actionSave"/>
+ <addaction name="actionSaveAs"/>
+ </widget>
+ <widget class="QToolBar" name="editToolBar">
+ <property name="windowTitle">
+ <string>Edit Tool Bar</string>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="separator"/>
+ <addaction name="actionAddElement"/>
+ <addaction name="actionRemoveElement"/>
+ </widget>
+ <widget class="QToolBar" name="zoomToolBar">
+ <property name="windowTitle">
+ <string>Zoom Tool Bar</string>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionZoomIn"/>
+ <addaction name="actionBestFit"/>
+ <addaction name="actionZoomOut"/>
+ </widget>
+ <widget class="QToolBar" name="analysisToolBar">
+ <property name="windowTitle">
+ <string>Analysis Tool Bar</string>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionSettings"/>
+ <addaction name="actionRun"/>
+ </widget>
+ <widget class="QDockWidget" name="modelDockWidget">
+ <property name="features">
+ <set>QDockWidget::AllDockWidgetFeatures</set>
+ </property>
+ <property name="windowTitle">
+ <string>Data</string>
+ </property>
+ <attribute name="dockWidgetArea">
+ <number>1</number>
+ </attribute>
+ <widget class="QWidget" name="dockWidgetContents">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTreeView" name="modelTree">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="animated">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <widget class="QDockWidget" name="reportDockWidget">
+ <property name="features">
+ <set>QDockWidget::AllDockWidgetFeatures</set>
+ </property>
+ <property name="windowTitle">
+ <string>Reports</string>
+ </property>
+ <attribute name="dockWidgetArea">
+ <number>1</number>
+ </attribute>
+ <widget class="QWidget" name="dockWidgetContents_2">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTreeWidget" name="reportTreeWidget">
+ <property name="animated">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string notr="true">1</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <action name="actionAboutQt">
+ <property name="icon">
+ <iconset theme="help-about">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>About &Qt</string>
+ </property>
+ <property name="statusTip">
+ <string>About the Qt toolkit</string>
+ </property>
+ </action>
+ <action name="actionAboutScram">
+ <property name="icon">
+ <iconset theme="help-about">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>About &SCRAM</string>
+ </property>
+ </action>
+ <action name="actionExit">
+ <property name="icon">
+ <iconset theme="application-exit">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>E&xit</string>
+ </property>
+ <property name="toolTip">
+ <string>Exit the Application</string>
+ </property>
+ </action>
+ <action name="actionNewModel">
+ <property name="icon">
+ <iconset theme="document-new">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>&New Model</string>
+ </property>
+ <property name="toolTip">
+ <string>Create a New Model</string>
+ </property>
+ </action>
+ <action name="actionOpenFiles">
+ <property name="icon">
+ <iconset theme="document-open">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>&Open Model Files...</string>
+ </property>
+ <property name="toolTip">
+ <string>Open Model Files</string>
+ </property>
+ </action>
+ <action name="actionSave">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="document-save">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>&Save Model</string>
+ </property>
+ </action>
+ <action name="actionSaveAs">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="document-save-as">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>Save Model &As...</string>
+ </property>
+ </action>
+ <action name="actionPrint">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="document-print">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>&Print...</string>
+ </property>
+ <property name="toolTip">
+ <string>Print</string>
+ </property>
+ </action>
+ <action name="actionExportAs">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="document-export">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>&Export As...</string>
+ </property>
+ </action>
+ <action name="actionZoomIn">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="zoom-in">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>Zoom &In</string>
+ </property>
+ </action>
+ <action name="actionZoomOut">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="zoom-out">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>Zoom &Out</string>
+ </property>
+ </action>
+ <action name="action400">
+ <property name="text">
+ <string>400%</string>
+ </property>
+ </action>
+ <action name="action200">
+ <property name="text">
+ <string>200%</string>
+ </property>
+ </action>
+ <action name="action150">
+ <property name="text">
+ <string>150%</string>
+ </property>
+ </action>
+ <action name="action125">
+ <property name="text">
+ <string>125%</string>
+ </property>
+ </action>
+ <action name="action100">
+ <property name="text">
+ <string>100%</string>
+ </property>
+ </action>
+ <action name="action85">
+ <property name="text">
+ <string>85%</string>
+ </property>
+ </action>
+ <action name="action50">
+ <property name="text">
+ <string>50%</string>
+ </property>
+ </action>
+ <action name="action70">
+ <property name="text">
+ <string>70%</string>
+ </property>
+ </action>
+ <action name="actionBestFit">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="zoom-fit-best">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>Best &Fit</string>
+ </property>
+ </action>
+ <action name="actionRun">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="utilities-terminal">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>&Run</string>
+ </property>
+ <property name="iconText">
+ <string>Run Analysis</string>
+ </property>
+ <property name="toolTip">
+ <string>Run Analysis</string>
+ </property>
+ <property name="shortcut">
+ <string>Alt+R</string>
+ </property>
+ </action>
+ <action name="actionSettings">
+ <property name="icon">
+ <iconset theme="applications-system">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>&Settings...</string>
+ </property>
+ <property name="iconText">
+ <string>Analysis Settings</string>
+ </property>
+ <property name="toolTip">
+ <string>Analysis Settings</string>
+ </property>
+ <property name="shortcut">
+ <string>Alt+S</string>
+ </property>
+ </action>
+ <action name="actionModelToolBar">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>&Model</string>
+ </property>
+ </action>
+ <action name="actionZoomToolBar">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>&Zoom</string>
+ </property>
+ </action>
+ <action name="actionAnalysisToolBar">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>&Analysis</string>
+ </property>
+ </action>
+ <action name="actionData">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>&Data</string>
+ </property>
+ </action>
+ <action name="actionReports">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>&Reports</string>
+ </property>
+ </action>
+ <action name="actionPrintPreview">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="document-print-preview">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>Print Previe&w...</string>
+ </property>
+ </action>
+ <action name="actionAddElement">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="list-add">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>&Add Element</string>
+ </property>
+ </action>
+ <action name="actionRemoveElement">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="list-remove">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>Re&move Element</string>
+ </property>
+ </action>
+ <action name="actionEditToolBar">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>&Edit</string>
+ </property>
+ </action>
+ <action name="actionExportReportAs">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="document-export">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>Export &Report As...</string>
+ </property>
+ </action>
+ <action name="actionRenameModel">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Re&name Model</string>
+ </property>
+ </action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
<include location="res.qrc"/>
</resources>
- <connections/>
+ <connections>
+ <connection>
+ <sender>actionModelToolBar</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>modelToolBar</receiver>
+ <slot>setVisible(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>319</x>
+ <y>44</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>modelToolBar</sender>
+ <signal>visibilityChanged(bool)</signal>
+ <receiver>actionModelToolBar</receiver>
+ <slot>setChecked(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>319</x>
+ <y>44</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionExit</sender>
+ <signal>triggered()</signal>
+ <receiver>MainWindow</receiver>
+ <slot>close()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>319</x>
+ <y>239</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>zoomToolBar</sender>
+ <signal>visibilityChanged(bool)</signal>
+ <receiver>actionZoomToolBar</receiver>
+ <slot>setChecked(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>217</x>
+ <y>44</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionZoomToolBar</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>zoomToolBar</receiver>
+ <slot>setVisible(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>217</x>
+ <y>44</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>analysisToolBar</sender>
+ <signal>visibilityChanged(bool)</signal>
+ <receiver>actionAnalysisToolBar</receiver>
+ <slot>setChecked(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>458</x>
+ <y>44</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionAnalysisToolBar</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>analysisToolBar</receiver>
+ <slot>setVisible(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>458</x>
+ <y>44</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionData</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>modelDockWidget</receiver>
+ <slot>setVisible(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>127</x>
+ <y>162</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>modelDockWidget</sender>
+ <signal>visibilityChanged(bool)</signal>
+ <receiver>actionData</receiver>
+ <slot>setChecked(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>127</x>
+ <y>162</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionReports</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>reportDockWidget</receiver>
+ <slot>setVisible(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>127</x>
+ <y>359</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>reportDockWidget</sender>
+ <signal>visibilityChanged(bool)</signal>
+ <receiver>actionReports</receiver>
+ <slot>setChecked(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>127</x>
+ <y>359</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionEditToolBar</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>editToolBar</receiver>
+ <slot>setVisible(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>204</x>
+ <y>44</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>editToolBar</sender>
+ <signal>visibilityChanged(bool)</signal>
+ <receiver>actionEditToolBar</receiver>
+ <slot>setChecked(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>204</x>
+ <y>44</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
</ui>
diff --git a/gui/model.cpp b/gui/model.cpp
new file mode 100644
index 0000000..2876185
--- /dev/null
+++ b/gui/model.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file model.cpp
+
+#include "model.h"
+
+#include <boost/range/algorithm.hpp>
+
+#include "src/fault_tree.h"
+
+#include "guiassert.h"
+
+namespace scram {
+namespace gui {
+namespace model {
+
+Element::SetLabel::SetLabel(Element *element, QString label)
+ : QUndoCommand(QObject::tr("Set element '%1' label to '%2'")
+ .arg(element->id(), label)),
+ m_label(std::move(label)), m_element(element)
+{
+}
+
+void Element::SetLabel::redo()
+{
+ QString cur_label = m_element->label();
+ if (m_label == cur_label)
+ return;
+ m_element->m_data->label(m_label.toStdString());
+ emit m_element->labelChanged(m_label);
+ m_label = std::move(cur_label);
+}
+
+BasicEvent::BasicEvent(mef::BasicEvent *basicEvent)
+ : Element(basicEvent), m_flavor(Flavor::Basic)
+{
+ if (basicEvent->HasAttribute("flavor")) {
+ const mef::Attribute &flavor = basicEvent->GetAttribute("flavor");
+ if (flavor.value == "undeveloped") {
+ m_flavor = Flavor::Undeveloped;
+ } else if (flavor.value == "conditional") {
+ m_flavor = Flavor::Conditional;
+ }
+ }
+}
+
+BasicEvent::SetExpression::SetExpression(BasicEvent *basicEvent,
+ mef::Expression *expression)
+ : QUndoCommand(QObject::tr("Modify basic event '%1' expression")
+ .arg(basicEvent->id())),
+ m_expression(expression), m_basicEvent(basicEvent)
+{
+}
+
+void BasicEvent::SetExpression::redo()
+{
+ auto *mefEvent = m_basicEvent->data();
+ mef::Expression *cur_expression
+ = mefEvent->HasExpression() ? &mefEvent->expression() : nullptr;
+ if (m_expression == cur_expression)
+ return;
+ mefEvent->expression(m_expression);
+ emit m_basicEvent->expressionChanged(m_expression);
+ m_expression = cur_expression;
+}
+
+BasicEvent::SetFlavor::SetFlavor(BasicEvent *basicEvent, Flavor flavor)
+ : QUndoCommand(tr("Set basic event '%1' flavor to '%2'")
+ .arg(basicEvent->id(), flavorToString(flavor))),
+ m_flavor(flavor), m_basicEvent(basicEvent)
+{
+}
+
+void BasicEvent::SetFlavor::redo()
+{
+ Flavor cur_flavor = m_basicEvent->flavor();
+ if (m_flavor == cur_flavor)
+ return;
+
+ mef::Element *mefEvent = m_basicEvent->data();
+ switch (m_flavor) {
+ case Basic:
+ mefEvent->RemoveAttribute("flavor");
+ break;
+ case Undeveloped:
+ mefEvent->SetAttribute({"flavor", "undeveloped", ""});
+ break;
+ case Conditional:
+ mefEvent->SetAttribute({"flavor", "conditional", ""});
+ break;
+ }
+ m_basicEvent->m_flavor = m_flavor;
+ emit m_basicEvent->flavorChanged(m_flavor);
+ m_flavor = cur_flavor;
+}
+
+HouseEvent::SetState::SetState(HouseEvent *houseEvent, bool state)
+ : QUndoCommand(QObject::tr("Set house event '%1' state to '%2'")
+ .arg(houseEvent->id(), boolToString(state))),
+ m_state(state), m_houseEvent(houseEvent)
+{
+}
+
+void HouseEvent::SetState::redo()
+{
+ if (m_state == m_houseEvent->state())
+ return;
+ bool prev_state = m_houseEvent->state();
+ m_houseEvent->data()->state(m_state);
+ emit m_houseEvent->stateChanged(m_state);
+ m_state = prev_state;
+}
+
+Gate::SetFormula::SetFormula(Gate *gate, mef::FormulaPtr formula)
+ : QUndoCommand(QObject::tr("Update gate '%1' formula").arg(gate->id())),
+ m_formula(std::move(formula)), m_gate(gate)
+{
+}
+
+void Gate::SetFormula::redo()
+{
+ m_formula = m_gate->data()->formula(std::move(m_formula));
+ emit m_gate->formulaChanged();
+}
+
+namespace {
+
+template <class T, class S>
+void populate(const mef::IdTable<S> &source, ProxyTable<T> *proxyTable)
+{
+ proxyTable->reserve(source.size());
+ for (const S &element : source)
+ proxyTable->emplace(std::make_unique<T>(element.get()));
+}
+
+} // namespace
+
+Model::Model(mef::Model *model) : Element(model), m_model(model)
+{
+ normalize(model);
+ populate<HouseEvent>(m_model->house_events(), &m_houseEvents);
+ populate<BasicEvent>(m_model->basic_events(), &m_basicEvents);
+ populate<Gate>(m_model->gates(), &m_gates);
+}
+
+void Model::normalize(mef::Model *model)
+{
+ for (const mef::FaultTreePtr &faultTree : model->fault_trees()) {
+ const_cast<mef::ElementTable<mef::BasicEvent *> &>(
+ faultTree->basic_events())
+ .clear();
+ const_cast<mef::ElementTable<mef::HouseEvent *> &>(
+ faultTree->house_events())
+ .clear();
+ }
+}
+
+std::vector<Gate *> Model::parents(mef::Formula::EventArg event) const
+{
+ std::vector<Gate *> result;
+ for (const std::unique_ptr<Gate> &gate : m_gates) {
+ if (boost::find(gate->args(), event) != gate->args().end())
+ result.push_back(gate.get());
+ }
+ return result;
+}
+
+Model::SetName::SetName(QString name, Model *model)
+ : QUndoCommand(QObject::tr("Rename model to '%1'").arg(name)),
+ m_model(model), m_name(std::move(name))
+{
+}
+
+void Model::SetName::redo()
+{
+ QString currentName = m_model->m_model->HasDefaultName()
+ ? QStringLiteral("")
+ : m_model->id();
+ if (currentName == m_name)
+ return;
+ m_model->m_model->SetOptionalName(m_name.toStdString());
+ emit m_model->modelNameChanged(m_name);
+ m_name = std::move(currentName);
+}
+
+Model::AddFaultTree::AddFaultTree(mef::FaultTreePtr faultTree, Model *model)
+ : QUndoCommand(QObject::tr("Add fault tree '%1'")
+ .arg(QString::fromStdString(faultTree->name()))),
+ m_model(model), m_address(faultTree.get()),
+ m_faultTree(std::move(faultTree))
+{
+}
+
+void Model::AddFaultTree::redo()
+{
+ m_model->m_model->Add(std::move(m_faultTree));
+ emit m_model->added(m_address);
+}
+
+void Model::AddFaultTree::undo()
+{
+ m_faultTree = m_model->m_model->Remove(m_address);
+ emit m_model->removed(m_address);
+}
+
+Model::RemoveFaultTree::RemoveFaultTree(mef::FaultTree *faultTree, Model *model)
+ : AddFaultTree(faultTree, model,
+ QObject::tr("Remove fault tree '%1'")
+ .arg(QString::fromStdString(faultTree->name())))
+{
+}
+
+} // namespace model
+} // namespace gui
+} // namespace scram
diff --git a/gui/model.h b/gui/model.h
new file mode 100644
index 0000000..de3b3f7
--- /dev/null
+++ b/gui/model.h
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file model.h
+/// Wrapper Model classes for the MEF data.
+
+#ifndef MODEL_H
+#define MODEL_H
+
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+
+#include <QObject>
+#include <QString>
+#include <QUndoCommand>
+#include <QVariant>
+
+#include "src/event.h"
+#include "src/ext/multi_index.h"
+#include "src/model.h"
+
+namespace scram {
+namespace gui {
+namespace model {
+
+/// Fault tree container element management assuming normalized model.
+/// @todo Move into an appropriate proxy type.
+/// @{
+inline void remove(mef::Event *, mef::FaultTree *) {}
+inline void remove(mef::Gate *gate, mef::FaultTree *faultTree)
+{
+ faultTree->Remove(gate);
+}
+inline void add(mef::Event *, mef::FaultTree *) {}
+inline void add(mef::Gate *gate, mef::FaultTree *faultTree)
+{
+ faultTree->Add(gate);
+}
+/// @}
+
+class Element : public QObject
+{
+ Q_OBJECT
+
+ template <class, class>
+ friend class Proxy; // Gets access to the data.
+
+public:
+ /// @returns A unique ID string for element within the element type-group.
+ ///
+ /// @pre The element is public.
+ QString id() const { return QString::fromStdString(m_data->name()); }
+
+ /// @returns The additional description for the element.
+ QString label() const { return QString::fromStdString(m_data->label()); }
+
+ class SetLabel : public QUndoCommand
+ {
+ public:
+ SetLabel(Element *element, QString label);
+
+ void redo() override;
+ void undo() override { redo(); }
+
+ private:
+ QString m_label;
+ Element *m_element;
+ };
+
+ template <class T>
+ class SetId : public QUndoCommand
+ {
+ public:
+ SetId(T *event, QString name, mef::Model *model,
+ mef::FaultTree *faultTree = nullptr)
+ : QUndoCommand(QObject::tr("Rename event '%1' to '%2'")
+ .arg(event->id(), name)),
+ m_name(std::move(name)), m_event(event), m_model(model),
+ m_faultTree(faultTree)
+ {
+ }
+
+ void undo() override { redo(); }
+ void redo() override
+ {
+ QString cur_name = m_event->id();
+ if (m_name == cur_name)
+ return;
+ if (m_faultTree)
+ remove(m_event->data(), m_faultTree);
+ auto ptr = m_model->Remove(m_event->data());
+ m_event->data()->id(m_name.toStdString());
+ if (m_faultTree)
+ add(m_event->data(), m_faultTree);
+ m_model->Add(std::move(ptr));
+ emit m_event->idChanged(m_name);
+ m_name = std::move(cur_name);
+ }
+
+ private:
+ QString m_name;
+ T *m_event;
+ mef::Model *m_model;
+ mef::FaultTree *m_faultTree;
+ };
+
+signals:
+ void labelChanged(const QString &label);
+ void idChanged(const QString &id);
+
+protected:
+ explicit Element(mef::Element *element) : m_data(element) {}
+
+private:
+ mef::Element *const m_data;
+};
+
+/// Provides the type and data of the origin for Proxy Elements.
+///
+/// @tparam E The Element class.
+/// @tparam T The MEF class.
+template <class E, class T>
+class Proxy
+{
+public:
+ using Origin = T; ///< The MEF type.
+
+ const T *data() const
+ {
+ return static_cast<const T *>(static_cast<const E *>(this)->m_data);
+ }
+ T *data()
+ {
+ return const_cast<T *>(static_cast<const Proxy *>(this)->data());
+ }
+};
+
+class BasicEvent : public Element, public Proxy<BasicEvent, mef::BasicEvent>
+{
+ Q_OBJECT
+
+public:
+ enum Flavor {
+ Basic = 0,
+ Undeveloped,
+ Conditional
+ };
+
+ static QString flavorToString(Flavor flavor)
+ {
+ switch (flavor) {
+ case Basic:
+ return tr("Basic");
+ case Undeveloped:
+ return tr("Undeveloped");
+ case Conditional:
+ return tr("Conditional");
+ }
+ assert(false);
+ }
+
+ explicit BasicEvent(mef::BasicEvent *basicEvent);
+
+ Flavor flavor() const { return m_flavor; }
+
+ /// @returns The current expression of this basic event.
+ /// nullptr if no expression has been set.
+ mef::Expression *expression() const
+ {
+ return data()->HasExpression() ? &data()->expression() : nullptr;
+ }
+
+ /// @returns The probability value of the event.
+ ///
+ /// @pre The basic event has expression.
+ template <typename T = double>
+ T probability() const { return data()->p(); }
+
+ /// Sets the basic event expression.
+ ///
+ /// @note Currently, the expression change
+ /// is detected with address comparison,
+ /// which may fail if the current expression has been changed.
+ class SetExpression : public QUndoCommand
+ {
+ public:
+ /// @param[in] basicEvent The basic event to receive an expression.
+ /// @param[in] expression The valid expression for the basic event.
+ /// nullptr to unset the expression.
+ SetExpression(BasicEvent *basicEvent, mef::Expression *expression);
+
+ void redo() override;
+ void undo() override { redo(); }
+
+ private:
+ mef::Expression *m_expression;
+ BasicEvent *m_basicEvent;
+ };
+
+ /// Sets the flavor of the basic event.
+ class SetFlavor : public QUndoCommand
+ {
+ public:
+ SetFlavor(BasicEvent *basicEvent, Flavor flavor);
+
+ void redo() override;
+ void undo() override { redo(); }
+
+ private:
+ Flavor m_flavor;
+ BasicEvent *m_basicEvent;
+ };
+
+signals:
+ void expressionChanged(mef::Expression *expression);
+ void flavorChanged(Flavor flavor);
+
+private:
+ Flavor m_flavor;
+};
+
+/// @returns The optional probability value of the basic event.
+template <>
+inline QVariant BasicEvent::probability<QVariant>() const
+{
+ return data()->HasExpression() ? QVariant(probability()) : QVariant();
+}
+
+class HouseEvent : public Element, public Proxy<HouseEvent, mef::HouseEvent>
+{
+ Q_OBJECT
+
+public:
+ explicit HouseEvent(mef::HouseEvent *houseEvent) : Element(houseEvent) {}
+
+ template <typename T = bool>
+ T state() const
+ {
+ return data()->state();
+ }
+
+ /// Flips the house event state.
+ class SetState : public QUndoCommand
+ {
+ public:
+ SetState(HouseEvent *houseEvent, bool state);
+
+ void redo() override;
+ void undo() override { redo(); }
+
+ private:
+ bool m_state;
+ HouseEvent *m_houseEvent;
+ };
+
+signals:
+ void stateChanged(bool value);
+};
+
+/// @returns String representation for the Boolean value.
+inline QString boolToString(bool value)
+{
+ return value ? QObject::tr("True") : QObject::tr("False");
+}
+
+/// @returns The string representation of the house event state.
+template <>
+inline QString HouseEvent::state<QString>() const
+{
+ return boolToString(state());
+}
+
+class Gate : public Element, public Proxy<Gate, mef::Gate>
+{
+ Q_OBJECT
+
+public:
+ explicit Gate(mef::Gate *gate) : Element(gate) {}
+
+ template <typename T = mef::Operator>
+ T type() const
+ {
+ return data()->formula().type();
+ }
+
+ int numArgs() const { return data()->formula().num_args(); }
+ int voteNumber() const
+ {
+ return data()->formula().vote_number();
+ }
+
+ const std::vector<mef::Formula::EventArg> &args() const
+ {
+ return data()->formula().event_args();
+ }
+
+ /// Formula modification commands.
+ class SetFormula : public QUndoCommand
+ {
+ public:
+ SetFormula(Gate *gate, mef::FormulaPtr formula);
+
+ void redo() override;
+ void undo() override { redo(); }
+
+ private:
+ mef::FormulaPtr m_formula;
+ Gate *m_gate;
+ };
+
+signals:
+ void formulaChanged();
+};
+
+template <>
+inline QString Gate::type() const
+{
+ switch (type()) {
+ case mef::kAnd:
+ return tr("and");
+ case mef::kOr:
+ return tr("or");
+ case mef::kVote:
+ return tr("at-least %1").arg(voteNumber());
+ case mef::kXor:
+ return tr("xor");
+ case mef::kNot:
+ return tr("not");
+ case mef::kNull:
+ return tr("null");
+ case mef::kNand:
+ return tr("nand");
+ case mef::kNor:
+ return tr("nor");
+ }
+ assert(false);
+}
+
+/// Table of proxy elements uniquely wrapping the core model element.
+///
+/// @tparam T The proxy type.
+template <class T, class M = typename T::Origin, class P = Proxy<T, M>>
+using ProxyTable = boost::multi_index_container<
+ std::unique_ptr<T>, boost::multi_index::indexed_by<
+ boost::multi_index::hashed_unique<boost::multi_index::const_mem_fun<
+ P, const M *, &P::data>>>>;
+
+/// The wrapper around the MEF Model.
+class Model : public Element, public Proxy<Model, mef::Model>
+{
+ Q_OBJECT
+
+public:
+ /// @param[in] model The analysis model with all constructs.
+ explicit Model(mef::Model *model);
+
+ const ProxyTable<HouseEvent> &houseEvents() const { return m_houseEvents; }
+ const ProxyTable<BasicEvent> &basicEvents() const { return m_basicEvents; }
+ const ProxyTable<Gate> &gates() const { return m_gates; }
+ const mef::ElementTable<mef::FaultTreePtr> &faultTrees() const
+ {
+ return m_model->fault_trees();
+ }
+ /// @returns The parent gates of an event.
+ std::vector<Gate *> parents(mef::Formula::EventArg event) const;
+
+ /// Model manipulation commands.
+ /// @{
+ class SetName : public QUndoCommand
+ {
+ public:
+ SetName(QString name, Model *model);
+
+ void redo() override;
+ void undo() override { redo(); }
+
+ private:
+ Model *m_model;
+ QString m_name;
+ };
+
+ /// @todo Provide a proxy class for the fault tree.
+ class AddFaultTree : public QUndoCommand
+ {
+ public:
+ AddFaultTree(mef::FaultTreePtr faultTree, Model *model);
+
+ void redo() override;
+ void undo() override;
+
+ protected:
+ AddFaultTree(mef::FaultTree *address, Model *model, QString description)
+ : QUndoCommand(std::move(description)), m_model(model),
+ m_address(address)
+ {
+ }
+
+ private:
+ Model *m_model;
+ mef::FaultTree *const m_address;
+ mef::FaultTreePtr m_faultTree;
+ };
+
+ class RemoveFaultTree : public AddFaultTree
+ {
+ public:
+ RemoveFaultTree(mef::FaultTree *faultTree, Model *model);
+
+ void redo() override { AddFaultTree::undo(); }
+ void undo() override { AddFaultTree::redo(); }
+ };
+
+ /// @tparam T The Model event type.
+ template <class T>
+ class AddEvent : public QUndoCommand
+ {
+ public:
+ AddEvent(std::unique_ptr<typename T::Origin> event, Model *model,
+ mef::FaultTree *faultTree = nullptr)
+ : QUndoCommand(QObject::tr("Add event '%1'")
+ .arg(QString::fromStdString(event->id()))),
+ m_model(model), m_proxy(std::make_unique<T>(event.get())),
+ m_address(event.get()), m_event(std::move(event)),
+ m_faultTree(faultTree)
+ {
+ }
+
+ void redo() override
+ {
+ m_model->m_model->Add(std::move(m_event));
+ auto it = m_model->table<T>().emplace(std::move(m_proxy)).first;
+ emit m_model->added(it->get());
+
+ if (m_faultTree)
+ add(m_address, m_faultTree);
+ }
+
+ void undo() override
+ {
+ m_event = m_model->m_model->Remove(m_address);
+ m_proxy = ext::extract(m_address, &m_model->table<T>());
+ emit m_model->removed(m_proxy.get());
+
+ if (m_faultTree)
+ remove(m_address, m_faultTree);
+ }
+
+ protected:
+ AddEvent(T *event, Model *model, mef::FaultTree *faultTree,
+ QString description)
+ : QUndoCommand(std::move(description)), m_model(model),
+ m_address(event->data()), m_faultTree(faultTree)
+ {
+ }
+
+ private:
+ Model *m_model;
+ std::unique_ptr<T> m_proxy;
+ typename T::Origin *const m_address;
+ std::unique_ptr<typename T::Origin> m_event;
+ mef::FaultTree *m_faultTree; ///< Optional container.
+ };
+
+ /// Removes an event from the model.
+ ///
+ /// @tparam T The proxy event type.
+ ///
+ /// @pre The event has no dependent/parent gates.
+ template <class T>
+ class RemoveEvent : public AddEvent<T>
+ {
+ static_assert(std::is_base_of<Element, T>::value, "");
+
+ public:
+ RemoveEvent(T *event, Model *model, mef::FaultTree *faultTree = nullptr)
+ : AddEvent<T>(event, model, faultTree,
+ QObject::tr("Remove event '%1'").arg(event->id()))
+ {
+ }
+
+ void redo() override { AddEvent<T>::undo(); }
+ void undo() override { AddEvent<T>::redo(); }
+ };
+
+ /// Changes the event type.
+ ///
+ /// @tparam E The type of the existing Model Event.
+ /// @tparam T The type of the new (target) Event.
+ template <class E, class T>
+ class ChangeEventType : public QUndoCommand
+ {
+ static_assert(!std::is_same<E, T>::value, "");
+ static_assert(std::is_base_of<Element, E>::value, "");
+ static_assert(std::is_base_of<Element, T>::value, "");
+
+ public:
+ /// Assumes that events have the same ID.
+ ChangeEventType(E *currentEvent,
+ std::unique_ptr<typename T::Origin> newEvent,
+ Model *model, mef::FaultTree *faultTree = nullptr)
+ : QUndoCommand(QObject::tr("Change the type of event '%1'")
+ .arg(currentEvent->id())),
+ m_switchTo{currentEvent, std::make_unique<T>(newEvent.get()),
+ std::move(newEvent)},
+ m_model(model), m_faultTree(faultTree),
+ m_gates(model->parents(currentEvent->data()))
+ {
+ }
+
+ void redo() override { m_switchFrom = m_switchTo(*this); }
+ void undo() override { m_switchTo = m_switchFrom(*this); }
+
+ private:
+ template <class Current, class Next>
+ struct Switch
+ {
+ Switch<Next, Current> operator()(const ChangeEventType &self)
+ {
+ std::unique_ptr<typename Current::Origin> curEvent
+ = self.m_model->m_model->Remove(m_address->data());
+ std::unique_ptr<Current> curProxy
+ = ext::extract(m_address->data(),
+ &self.m_model->template table<Current>());
+ emit self.m_model->removed(m_address);
+ Next *nextAddress = m_proxy.get();
+ self.m_model->m_model->Add(std::move(m_event));
+ self.m_model->template table<Next>().emplace(
+ std::move(m_proxy));
+ emit self.m_model->added(nextAddress);
+ if (self.m_faultTree) {
+ remove(m_address->data(), self.m_faultTree);
+ add(nextAddress->data(), self.m_faultTree);
+ }
+ for (Gate *gate : self.m_gates) {
+ gate->data()->formula().RemoveArgument(m_address->data());
+ gate->data()->formula().AddArgument(nextAddress->data());
+ }
+ for (Gate *gate : self.m_gates)
+ emit gate->formulaChanged();
+
+ return {nextAddress, std::move(curProxy), std::move(curEvent)};
+ }
+
+ Current *m_address;
+ std::unique_ptr<Next> m_proxy;
+ std::unique_ptr<typename Next::Origin> m_event;
+ };
+ Switch<E, T> m_switchTo;
+ Switch<T, E> m_switchFrom;
+
+ Model *m_model;
+ mef::FaultTree *m_faultTree;
+ std::vector<Gate *> m_gates;
+ };
+ /// @}
+
+signals:
+ void modelNameChanged(QString name);
+ void added(mef::FaultTree *faultTree);
+ void added(HouseEvent *houseEvent);
+ void added(BasicEvent *basicEvent);
+ void added(Gate *gate);
+ void removed(mef::FaultTree *faultTree);
+ void removed(HouseEvent *houseEvent);
+ void removed(BasicEvent *basicEvent);
+ void removed(Gate *gate);
+
+private:
+ /// Normalizes the model to the GUI expectations.
+ ///
+ /// @post No house events or basic events in fault tree containers.
+ ///
+ /// @todo Remove normalization upon full container support for elements.
+ void normalize(mef::Model *model);
+
+ template <class T>
+ ProxyTable<T> &table();
+
+ mef::Model *m_model;
+ ProxyTable<HouseEvent> m_houseEvents;
+ ProxyTable<BasicEvent> m_basicEvents;
+ ProxyTable<Gate> m_gates;
+};
+
+template <>
+inline ProxyTable<Gate> &Model::table<Gate>() { return m_gates; }
+template <>
+inline ProxyTable<BasicEvent> &Model::table<BasicEvent>()
+{
+ return m_basicEvents;
+}
+template <>
+inline ProxyTable<HouseEvent> &Model::table<HouseEvent>()
+{
+ return m_houseEvents;
+}
+
+} // namespace model
+} // namespace gui
+} // namespace scram
+
+#endif // MODEL_H
diff --git a/gui/modeltree.cpp b/gui/modeltree.cpp
new file mode 100644
index 0000000..aec63e6
--- /dev/null
+++ b/gui/modeltree.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "modeltree.h"
+
+#include "guiassert.h"
+#include "overload.h"
+
+namespace scram {
+namespace gui {
+
+ModelTree::ModelTree(model::Model *model, QObject *parent)
+ : QAbstractItemModel(parent), m_model(model)
+{
+ for (const mef::FaultTreePtr &faultTree : m_model->faultTrees())
+ m_faultTrees.insert(faultTree.get());
+
+ connect(m_model, OVERLOAD(model::Model, added, mef::FaultTree *), this,
+ [this](mef::FaultTree *faultTree) {
+ auto it = m_faultTrees.lower_bound(faultTree);
+ int index = m_faultTrees.index_of(it);
+ beginInsertRows(
+ this->index(static_cast<int>(Row::FaultTrees), 0, {}),
+ index, index);
+ m_faultTrees.insert(it, faultTree);
+ endInsertRows();
+ });
+ connect(m_model, OVERLOAD(model::Model, removed, mef::FaultTree *), this,
+ [this](mef::FaultTree *faultTree) {
+ auto it = m_faultTrees.find(faultTree);
+ GUI_ASSERT(it != m_faultTrees.end(), );
+ int index = m_faultTrees.index_of(it);
+ beginRemoveRows(
+ this->index(static_cast<int>(Row::FaultTrees), 0, {}),
+ index, index);
+ m_faultTrees.erase(it);
+ endRemoveRows();
+ });
+}
+
+int ModelTree::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid() == false)
+ return 4;
+ if (parent.parent().isValid())
+ return 0;
+ if (static_cast<Row>(parent.row()) == Row::FaultTrees)
+ return m_faultTrees.size();
+ return 0;
+}
+
+int ModelTree::columnCount(const QModelIndex &parent) const
+{
+ if (parent.isValid() == false)
+ return 1;
+ if (parent.parent().isValid())
+ return 0;
+ if (static_cast<Row>(parent.row()) == Row::FaultTrees)
+ return 1;
+ return 0;
+}
+
+QModelIndex ModelTree::index(int row, int column,
+ const QModelIndex &parent) const
+{
+ if (parent.isValid() == false)
+ return createIndex(row, column, nullptr);
+ GUI_ASSERT(parent.parent().isValid() == false, {});
+ GUI_ASSERT(static_cast<Row>(parent.row()) == Row::FaultTrees, {});
+ return createIndex(row, column, *m_faultTrees.nth(row));
+}
+
+QModelIndex ModelTree::parent(const QModelIndex &index) const
+{
+ GUI_ASSERT(index.isValid(), {});
+ if (index.internalPointer() == nullptr)
+ return {};
+ return createIndex(static_cast<int>(Row::FaultTrees), 0, nullptr);
+}
+
+QVariant ModelTree::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || role != Qt::DisplayRole)
+ return {};
+ if (index.parent().isValid())
+ return QString::fromStdString(
+ static_cast<mef::FaultTree *>(index.internalPointer())->name());
+ switch (static_cast<Row>(index.row())) {
+ case Row::FaultTrees:
+ return tr("Fault Trees");
+ case Row::Gates:
+ return tr("Gates");
+ case Row::BasicEvents:
+ return tr("Basic Events");
+ case Row::HouseEvents:
+ return tr("House Events");
+ }
+ GUI_ASSERT(false, {});
+}
+
+} // namespace gui
+} // namespace scram
diff --git a/gui/modeltree.h b/gui/modeltree.h
new file mode 100644
index 0000000..dfe6823
--- /dev/null
+++ b/gui/modeltree.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file modeltree.h
+/// The main tree representation of the Model.
+
+#ifndef MODELTREE_H
+#define MODELTREE_H
+
+#include <QAbstractItemModel>
+
+#include <boost/container/flat_set.hpp>
+
+#include "src/element.h"
+#include "src/fault_tree.h"
+
+#include "model.h"
+
+namespace scram {
+namespace gui {
+
+class ModelTree : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ /// The top row containers of the tree.
+ enum class Row {
+ FaultTrees,
+ Gates,
+ BasicEvents,
+ HouseEvents
+ };
+
+ explicit ModelTree(model::Model *model, QObject *parent = nullptr);
+
+ int rowCount(const QModelIndex &parent) const override;
+ int columnCount(const QModelIndex &parent) const override;
+ QModelIndex index(int row, int column,
+ const QModelIndex &parent) const override;
+ QModelIndex parent(const QModelIndex &index) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+
+private:
+ struct NameComparator {
+ bool operator()(const mef::Element *lhs, const mef::Element *rhs) const
+ {
+ return lhs->name() < rhs->name();
+ }
+ };
+
+ model::Model *m_model;
+ boost::container::flat_set<mef::FaultTree *, NameComparator> m_faultTrees;
+};
+
+} // namespace gui
+} // namespace scram
+
+#endif // MODELTREE_H
diff --git a/gui/namedialog.ui b/gui/namedialog.ui
new file mode 100644
index 0000000..e72379f
--- /dev/null
+++ b/gui/namedialog.ui
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NameDialog</class>
+ <widget class="QDialog" name="NameDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>216</width>
+ <height>80</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Name Edit</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="nameLine"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>NameDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>NameDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/gui/gate.h b/gui/overload.h
similarity index 65%
rename from gui/gate.h
rename to gui/overload.h
index 11f6c7b..6922f6c 100644
--- a/gui/gate.h
+++ b/gui/overload.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Olzhas Rakhimov
+ * Copyright (C) 2017 Olzhas Rakhimov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,22 +15,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef GATE_H
-#define GATE_H
-
-#include <QGraphicsItem>
-
-namespace scram {
-namespace gui {
-
-/**
- * @brief The abstract base class for various gate types.
- */
-class Gate : public QGraphicsItem
-{
-};
-
-} // namespace gui
-} // namespace scram
-
-#endif // GATE_H
+/// Helper macro to resolve overloaded signals in Qt 5 style connections.
+///
+/// @param T The class type of the signal.
+/// @param name The name of the signal function.
+/// @param ... The parameter types of the signal function.
+#define OVERLOAD(T, name, ...) static_cast<void (T::*)(__VA_ARGS__)>(&T::name)
diff --git a/gui/mainwindow.h b/gui/printable.cpp
similarity index 54%
copy from gui/mainwindow.h
copy to gui/printable.cpp
index f6872c8..a1e9e99 100644
--- a/gui/mainwindow.h
+++ b/gui/printable.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2017 Olzhas Rakhimov
+ * Copyright (C) 2017 Olzhas Rakhimov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,41 +15,34 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef MAINWINDOW_H
-#define MAINWINDOW_H
+/// @file printable.cpp
+/// Implementation of default print functionality.
-#include <string>
-#include <vector>
+#include "printable.h"
-#include <QMainWindow>
-
-namespace Ui {
-class MainWindow;
-}
+#include <QObject>
+#include <QPrintDialog>
+#include <QPrintPreviewDialog>
namespace scram {
namespace gui {
-class MainWindow : public QMainWindow
+void Printable::print()
{
- Q_OBJECT
-
-public:
- explicit MainWindow(QWidget *parent = nullptr);
- ~MainWindow();
-
- void setConfig(const std::string &config)
- {
- m_config = QString::fromStdString(config);
- }
- void addInputFiles(const std::vector<std::string>& /*input_files*/) {}
+ QPrinter printer;
+ QPrintDialog dialog(&printer);
+ if (dialog.exec() == QDialog::Accepted)
+ this->doPrint(&printer);
+}
-private:
- Ui::MainWindow *ui;
- QString m_config; ///< The main project configuration file.
-};
+void Printable::printPreview()
+{
+ QPrinter printer;
+ QPrintPreviewDialog preview(&printer);
+ QObject::connect(&preview, &QPrintPreviewDialog::paintRequested,
+ [this, &printer] { this->doPrint(&printer); });
+ preview.exec();
+}
} // namespace gui
} // namespace scram
-
-#endif // MAINWINDOW_H
diff --git a/gui/gate.cpp b/gui/printable.h
similarity index 59%
rename from gui/gate.cpp
rename to gui/printable.h
index 2b7ce33..80c6242 100644
--- a/gui/gate.cpp
+++ b/gui/printable.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Olzhas Rakhimov
+ * Copyright (C) 2017 Olzhas Rakhimov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,10 +15,33 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "gate.h"
+/// @file printable.h
+/// Interface for printable objects.
+
+#ifndef PRINTABLE_H
+#define PRINTABLE_H
+
+#include <QPrinter>
namespace scram {
namespace gui {
+class Printable {
+public:
+ virtual ~Printable() = default;
+
+ /// Prints with a print dialog.
+ void print();
+ /// Prints with a print preview dialog.
+ void printPreview();
+
+private:
+ /// The actual printing must be implemented by derived classes.
+ virtual void doPrint(QPrinter *printer) = 0;
+
+};
+
} // namespace gui
} // namespace scram
+
+#endif // PRINTABLE_H
diff --git a/gui/res.qrc b/gui/res.qrc
index a97d524..0d0722a 100644
--- a/gui/res.qrc
+++ b/gui/res.qrc
@@ -1,5 +1,5 @@
<RCC>
<qresource prefix="/">
- <file>images/scram_logo.png</file>
+ <file>images/scram_solid128x128.png</file>
</qresource>
</RCC>
diff --git a/gui/scram-gui.desktop b/gui/scram-gui.desktop
new file mode 100644
index 0000000..43e737e
--- /dev/null
+++ b/gui/scram-gui.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Name=SCRAM
+Comment=Probabilistic risk analysis tool
+TryExec=scram-gui
+Exec=scram-gui %F
+Icon=scram
+Terminal=false
+Type=Application
+Categories=Science;Engineering;
diff --git a/gui/scram.ico b/gui/scram.ico
new file mode 100644
index 0000000..c62522f
Binary files /dev/null and b/gui/scram.ico differ
diff --git a/gui/scram.rc b/gui/scram.rc
new file mode 100644
index 0000000..afa8fad
--- /dev/null
+++ b/gui/scram.rc
@@ -0,0 +1 @@
+IDI_ICON1 ICON DISCARDABLE "scram.ico"
diff --git a/gui/scramgui.cpp b/gui/scramgui.cpp
new file mode 100644
index 0000000..ec4f600
--- /dev/null
+++ b/gui/scramgui.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2015-2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file scramgui.cpp
+/// The main entrance to the SCRAM GUI.
+///
+/// This approach explicitly separates the build dependencies
+/// from the actual main() entrance function,
+/// for there are weird dependency linking problems on Windows.
+
+#include <csignal>
+
+#include <exception>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <QApplication>
+#include <QCoreApplication>
+#include <QIcon>
+#include <QMessageBox>
+#include <QString>
+
+#include <boost/program_options.hpp>
+
+#include "mainwindow.h"
+
+#include "src/error.h"
+#include "src/version.h"
+
+#ifdef Q_OS_WIN
+#include <QtPlugin>
+Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
+#endif
+
+namespace po = boost::program_options;
+
+namespace {
+
+/**
+ * Parses the command-line arguments.
+ *
+ * @param[in] argc Count of arguments.
+ * @param[in] argv Values of arguments.
+ * @param[out] vm Variables map of program options.
+ *
+ * @returns 0 for success.
+ * @returns 1 for errored state.
+ * @returns -1 for information only state like help and version.
+ */
+int parseArguments(int argc, char *argv[], po::variables_map *vm) noexcept
+{
+ const char* usage = "Usage: scram-gui [options] [input-files]...";
+ po::options_description desc("Options");
+ desc.add_options()
+ ("help", "Display this help message")
+ ("config-file", po::value<std::string>()->value_name("path"),
+ "Project configuration file");
+ try {
+ po::store(po::parse_command_line(argc, argv, desc), *vm);
+ } catch (std::exception &err) {
+ std::cerr << "Option error: " << err.what() << "\n\n"
+ << usage << "\n\n"
+ << desc << "\n";
+ return 1;
+ }
+
+ po::notify(*vm);
+
+ // Process command-line arguments.
+ if (vm->count("help")) {
+ std::cout << usage << "\n\n" << desc << "\n";
+ return -1;
+ }
+ desc.add_options()("input-files", po::value<std::vector<std::string>>());
+ po::positional_options_description p;
+ p.add("input-files", -1);
+
+ po::store(
+ po::command_line_parser(argc, argv).options(desc).positional(p).run(),
+ *vm);
+ po::notify(*vm);
+ return 0;
+}
+
+/// Guards the application from crashes on escaped internal exceptions.
+class GuardedApplication : public QApplication {
+public:
+ using QApplication::QApplication;
+
+ bool notify(QObject *receiver, QEvent *event) override
+ {
+ try {
+ return QApplication::notify(receiver, event);
+ } catch (const scram::Error &err) {
+ qCritical("%s", err.what());
+ QMessageBox::critical(nullptr, tr("Internal SCRAM Error"),
+ QString::fromUtf8(err.what()));
+ } catch (const std::exception &err) {
+ qCritical("%s", err.what());
+ QMessageBox::critical(nullptr, tr("Internal Exception Error"),
+ QString::fromUtf8(err.what()));
+ } catch (...) {
+ qCritical("Unknown exception type.");
+ QMessageBox::critical(nullptr, tr("Internal Exception Error"),
+ tr("Unknown exception type."));
+ }
+ return false;
+ }
+};
+
+/// Produces the crash dialog with a given reasoning.
+/// The dialog allows access to other windows
+/// so that users may try saving the model before the crash.
+void crashDialog(const QString &text) noexcept
+{
+ QMessageBox message(QMessageBox::Critical,
+ QObject::tr("Unrecoverable Internal Error"), text,
+ QMessageBox::Ok);
+ message.setWindowModality(Qt::WindowModal);
+ message.exec();
+}
+
+/// Attempts to inform about imminent crash due to internal errors.
+void crashHandler(int signum) noexcept
+{
+ switch (signum) {
+ case SIGSEGV:
+ crashDialog(QObject::tr("SIGSEGV: Invalid memory access."));
+ break;
+ case SIGFPE:
+ crashDialog(QObject::tr("SIGFPE: Erroneous arithmetic operation."));
+ break;
+ case SIGILL:
+ crashDialog(QObject::tr("SIGILL: Illegal instruction."));
+ break;
+ }
+ std::signal(signum, SIG_DFL);
+ std::raise(signum);
+}
+
+/// Preserve the global default before setting a new terminate handler.
+static const std::terminate_handler gDefaultTerminateHandler
+ = std::get_terminate();
+
+/// Pulls the exception message into GUI before crash.
+void terminateHandler() noexcept
+{
+ QString error;
+ try {
+ std::rethrow_exception(std::current_exception());
+ } catch (const scram::Error &err) {
+ error = QObject::tr("SCRAM exception: %1")
+ .arg(QString::fromUtf8(err.what()));
+ } catch (const std::exception &err) {
+ error = QObject::tr("Standard exception: %1")
+ .arg(QString::fromUtf8(err.what()));
+ } catch (...) {
+ error = QObject::tr("Exception of unknown type: no message available.");
+ }
+ crashDialog(
+ QObject::tr("Exception no-throw contract violation:\n\n%1").arg(error));
+ gDefaultTerminateHandler();
+}
+
+/// Installs crash handlers for system signals.
+void installCrashHandlers() noexcept
+{
+ std::signal(SIGSEGV, crashHandler);
+ std::signal(SIGFPE, crashHandler);
+ std::signal(SIGILL, crashHandler);
+ std::set_terminate(terminateHandler);
+}
+
+} // namespace
+
+/**
+ * The main launcher for the SCRAM GUI.
+ * This function must be called by the main function.
+ */
+int launchGui(int argc, char *argv[])
+{
+ // Keep the following commented code!
+ // In some static build configurations,
+ // the resources may fail to load.
+ // However, the most distributions are expected to be shared builds,
+ // so the explicit load should not be used, but it is kept for debugging.
+ /* Q_INIT_RESOURCE(res); */
+
+ installCrashHandlers();
+
+ QCoreApplication::setOrganizationName(QString::fromLatin1("scram"));
+ QCoreApplication::setOrganizationDomain(
+ QString::fromLatin1("scram-pra.org"));
+ QCoreApplication::setApplicationName(QString::fromLatin1("scram"));
+ QCoreApplication::setApplicationVersion(
+ QString::fromLatin1(scram::version::core()));
+
+ GuardedApplication a(argc, argv);
+
+ if (QIcon::themeName().isEmpty())
+ QIcon::setThemeName(QStringLiteral("tango"));
+
+ scram::gui::MainWindow w;
+ w.show();
+
+ if (argc > 1) {
+ po::variables_map vm;
+ int ret = parseArguments(argc, argv, &vm);
+ if (ret == 1)
+ return 1;
+ if (ret == -1)
+ return 0;
+ std::vector<std::string> inputFiles;
+ try {
+ if (vm.count("input-files"))
+ inputFiles = vm["input-files"].as<std::vector<std::string>>();
+ if (vm.count("config-file")) {
+ w.setConfig(vm["config-file"].as<std::string>(), inputFiles);
+ } else {
+ w.addInputFiles(inputFiles);
+ }
+ } catch (boost::exception &) { assert(false); }
+ }
+ return a.exec();
+}
diff --git a/gui/settingsdialog.cpp b/gui/settingsdialog.cpp
new file mode 100644
index 0000000..3f641be
--- /dev/null
+++ b/gui/settingsdialog.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "settingsdialog.h"
+#include "ui_settingsdialog.h"
+
+#include "src/error.h"
+
+#include "guiassert.h"
+
+namespace scram {
+namespace gui {
+
+SettingsDialog::SettingsDialog(const core::Settings &initSettings,
+ QWidget *parent)
+ : QDialog(parent), ui(new Ui::SettingsDialog)
+{
+ ui->setupUi(this);
+ setupState(initSettings);
+ setupConnections();
+}
+
+SettingsDialog::~SettingsDialog() = default;
+
+core::Settings SettingsDialog::settings() const
+{
+ core::Settings result;
+ try {
+ if (ui->bdd->isChecked()) {
+ result.algorithm(core::Algorithm::kBdd);
+ } else if (ui->zbdd->isChecked()) {
+ result.algorithm(core::Algorithm::kZbdd);
+ } else {
+ GUI_ASSERT(ui->mocus->isChecked(), result);
+ result.algorithm(core::Algorithm::kMocus);
+ }
+
+ result.prime_implicants(ui->primeImplicants->isChecked());
+ result.probability_analysis(ui->probability->isChecked());
+ result.importance_analysis(ui->importance->isChecked());
+
+ if (ui->approximationsBox->isChecked() == false) {
+ result.approximation(core::Approximation::kNone);
+ } else if (ui->rareEvent->isChecked()) {
+ result.approximation(core::Approximation::kRareEvent);
+ } else {
+ GUI_ASSERT(ui->mcub->isChecked(), result);
+ result.approximation(core::Approximation::kMcub);
+ }
+
+ result.limit_order(ui->productOrder->value());
+ result.mission_time(ui->missionTime->value());
+ } catch (Error& err) {
+ GUI_ASSERT(false && err.what(), result);
+ }
+ return result;
+}
+
+void SettingsDialog::setupState(const core::Settings &initSettings)
+{
+ ui->primeImplicants->setChecked(initSettings.prime_implicants());
+ ui->probability->setChecked(initSettings.probability_analysis());
+ ui->importance->setChecked(initSettings.importance_analysis());
+ ui->missionTime->setValue(initSettings.mission_time());
+ ui->productOrder->setValue(initSettings.limit_order());
+
+ switch (initSettings.algorithm()) {
+ case core::Algorithm::kBdd:
+ ui->bdd->setChecked(true);
+ break;
+ case core::Algorithm::kZbdd:
+ ui->zbdd->setChecked(true);
+ break;
+ case core::Algorithm::kMocus:
+ ui->mocus->setChecked(true);
+ break;
+ }
+
+ ui->approximationsBox->setChecked(true);
+ switch (initSettings.approximation()) {
+ case core::Approximation::kNone:
+ ui->approximationsBox->setChecked(false);
+ break;
+ case core::Approximation::kRareEvent:
+ ui->rareEvent->setChecked(true);
+ break;
+ case core::Approximation::kMcub:
+ ui->mcub->setChecked(true);
+ break;
+ }
+}
+
+void SettingsDialog::setupConnections()
+{
+ connect(ui->probability, &QAbstractButton::toggled, [this](bool checked) {
+ if (!checked)
+ ui->importance->setChecked(false);
+ });
+ connect(ui->importance, &QAbstractButton::toggled, [this](bool checked) {
+ if (checked)
+ ui->probability->setChecked(true);
+ });
+ connect(ui->bdd, &QAbstractButton::toggled, [this](bool checked) {
+ if (!checked) {
+ ui->approximationsBox->setChecked(true);
+ ui->primeImplicants->setChecked(false);
+ }
+ });
+ connect(ui->primeImplicants, &QAbstractButton::toggled,
+ [this](bool checked) {
+ if (checked) {
+ ui->bdd->setChecked(true);
+ ui->approximationsBox->setChecked(false);
+ }
+ });
+ connect(ui->approximationsBox, &QGroupBox::toggled, [this](bool checked) {
+ if (checked) {
+ ui->primeImplicants->setChecked(false);
+ } else {
+ ui->bdd->setChecked(true);
+ }
+ });
+}
+
+} // namespace gui
+} // namespace scram
diff --git a/gui/mainwindow.h b/gui/settingsdialog.h
similarity index 53%
copy from gui/mainwindow.h
copy to gui/settingsdialog.h
index f6872c8..2ab1db2 100644
--- a/gui/mainwindow.h
+++ b/gui/settingsdialog.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2017 Olzhas Rakhimov
+ * Copyright (C) 2017 Olzhas Rakhimov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,41 +15,43 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef MAINWINDOW_H
-#define MAINWINDOW_H
+#ifndef SETTINGSDIALOG_H
+#define SETTINGSDIALOG_H
-#include <string>
-#include <vector>
+#include <memory>
-#include <QMainWindow>
+#include <QDialog>
+
+#include "src/settings.h"
namespace Ui {
-class MainWindow;
+class SettingsDialog;
}
namespace scram {
namespace gui {
-class MainWindow : public QMainWindow
+class SettingsDialog : public QDialog
{
Q_OBJECT
public:
- explicit MainWindow(QWidget *parent = nullptr);
- ~MainWindow();
+ /// @param[in] initSettings The initial settings to setup the dialog.
+ explicit SettingsDialog(const core::Settings &initSettings,
+ QWidget *parent = nullptr);
+ ~SettingsDialog();
- void setConfig(const std::string &config)
- {
- m_config = QString::fromStdString(config);
- }
- void addInputFiles(const std::vector<std::string>& /*input_files*/) {}
+ /// @returns Analysis settings derived from the dialog state.
+ core::Settings settings() const;
private:
- Ui::MainWindow *ui;
- QString m_config; ///< The main project configuration file.
+ void setupState(const core::Settings &initSettings);
+ void setupConnections();
+
+ std::unique_ptr<Ui::SettingsDialog> ui;
};
} // namespace gui
} // namespace scram
-#endif // MAINWINDOW_H
+#endif // SETTINGSDIALOG_H
diff --git a/gui/settingsdialog.ui b/gui/settingsdialog.ui
new file mode 100644
index 0000000..c798bdd
--- /dev/null
+++ b/gui/settingsdialog.ui
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SettingsDialog</class>
+ <widget class="QDialog" name="SettingsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>452</width>
+ <height>267</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Analysis Settings</string>
+ </property>
+ <property name="windowIcon">
+ <iconset theme="applications-system">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="approximationsBox">
+ <property name="title">
+ <string>Approximations</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QRadioButton" name="rareEvent">
+ <property name="text">
+ <string>Rare-event</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="mcub">
+ <property name="text">
+ <string>MCUB</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="analysesBox">
+ <property name="title">
+ <string>Analyses</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QCheckBox" name="primeImplicants">
+ <property name="text">
+ <string>Prime implicants</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="probability">
+ <property name="text">
+ <string>Probability</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="importance">
+ <property name="text">
+ <string>Importance</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QGroupBox" name="algorithmsBox">
+ <property name="title">
+ <string>Algorithms</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QRadioButton" name="mocus">
+ <property name="text">
+ <string>MOCUS</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="bdd">
+ <property name="text">
+ <string>BDD</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="zbdd">
+ <property name="text">
+ <string>ZBDD</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QGroupBox" name="limitsBox">
+ <property name="title">
+ <string>Limits</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelMissionTime">
+ <property name="text">
+ <string>Mission time:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QDoubleSpinBox" name="missionTime">
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="minimum">
+ <double>1.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>1000000000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelProductOrder">
+ <property name="text">
+ <string>Product order:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="productOrder"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>primeImplicants</tabstop>
+ <tabstop>probability</tabstop>
+ <tabstop>importance</tabstop>
+ <tabstop>mocus</tabstop>
+ <tabstop>bdd</tabstop>
+ <tabstop>zbdd</tabstop>
+ <tabstop>approximationsBox</tabstop>
+ <tabstop>rareEvent</tabstop>
+ <tabstop>mcub</tabstop>
+ <tabstop>missionTime</tabstop>
+ <tabstop>productOrder</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/gui/startpage.ui b/gui/startpage.ui
new file mode 100644
index 0000000..27ad9bc
--- /dev/null
+++ b/gui/startpage.ui
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>StartPage</class>
+ <widget class="QWidget" name="StartPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Start Here</string>
+ </property>
+ <property name="windowIcon">
+ <iconset theme="start-here">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <layout class="QVBoxLayout" name="buttons">
+ <item>
+ <widget class="QCommandLinkButton" name="newModelButton">
+ <property name="text">
+ <string>Start a New Model</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCommandLinkButton" name="openModelButton">
+ <property name="text">
+ <string>Open Model Files</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCommandLinkButton" name="exampleModelsButton">
+ <property name="text">
+ <string>Example Models</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="1">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/gui/zoomableview.cpp b/gui/zoomableview.cpp
new file mode 100644
index 0000000..8a45bbd
--- /dev/null
+++ b/gui/zoomableview.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file zoomableview.cpp
+
+#include "zoomableview.h"
+
+#include <QMatrix>
+
+namespace scram {
+namespace gui {
+
+const int ZoomableView::m_minZoomLevel = 10;
+
+void ZoomableView::setZoom(int level)
+{
+ if (level == m_zoom)
+ return;
+ if (level < m_minZoomLevel)
+ level = m_minZoomLevel;
+
+ double scaleValue = 0.01 * level;
+ QMatrix matrix;
+ matrix.scale(scaleValue, scaleValue);
+ this->setMatrix(matrix);
+ m_zoom = level;
+
+ emit zoomChanged(level);
+}
+
+void ZoomableView::zoomBestFit()
+{
+ QSize viewSize = size();
+ QSize sceneSize = scene()->sceneRect().size().toSize();
+ double ratioHeight
+ = static_cast<double>(viewSize.height()) / sceneSize.height();
+ double ratioWidth
+ = static_cast<double>(viewSize.width()) / sceneSize.width();
+ setZoom(std::min(ratioHeight, ratioWidth) * 100);
+}
+
+void ZoomableView::wheelEvent(QWheelEvent *event)
+{
+ if (event->modifiers() & Qt::ControlModifier) {
+ if (event->delta() > 0)
+ zoomIn(5);
+ else
+ zoomOut(5);
+ event->accept();
+ } else {
+ QGraphicsView::wheelEvent(event);
+ }
+}
+
+} // namespace gui
+} // namespace scram
diff --git a/gui/zoomableview.h b/gui/zoomableview.h
new file mode 100644
index 0000000..efe38cb
--- /dev/null
+++ b/gui/zoomableview.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file zoomableview.h
+/// Provides a GraphicsView with zoom in/out and other convenience features.
+
+#ifndef ZOOMABLEVIEW_H
+#define ZOOMABLEVIEW_H
+
+#include <QGraphicsView>
+#include <QWheelEvent>
+
+namespace scram {
+namespace gui {
+
+class ZoomableView : public QGraphicsView
+{
+ Q_OBJECT
+
+public:
+ using QGraphicsView::QGraphicsView;
+
+ /// @returns The zoom value in percentages.
+ int getZoom() const { return m_zoom; }
+
+signals:
+ void zoomChanged(int level);
+
+public slots:
+ void setZoom(int level);
+ void zoomIn(int deltaLevel) { setZoom(m_zoom + deltaLevel); }
+ void zoomOut(int deltaLevel) { setZoom(m_zoom - deltaLevel); }
+ void zoomBestFit();
+
+protected:
+ void wheelEvent(QWheelEvent *event) override;
+
+private:
+ static const int m_minZoomLevel;
+
+ int m_zoom = 100; ///< The value is in percents.
+};
+
+} // namespace gui
+} // namespace scram
+
+#endif // ZOOMABLEVIEW_H
diff --git a/input/TwoTrain/two_train.xml b/input/TwoTrain/two_train.xml
index df41729..10722cc 100644
--- a/input/TwoTrain/two_train.xml
+++ b/input/TwoTrain/two_train.xml
@@ -1,5 +1,5 @@
<?xml version="1.0"?>
-<opsa-mef>
+<opsa-mef name="TwoTrains">
<define-fault-tree name="TwoTrains">
<define-gate name="TopEvent">
<and>
diff --git a/share/gui.rng b/share/gui.rng
index 76bd8ab..30edb10 100644
--- a/share/gui.rng
+++ b/share/gui.rng
@@ -23,7 +23,6 @@
<zeroOrMore>
<choice>
<ref name="fault-tree-definition"/>
- <ref name="CCF-group-definition"/>
</choice>
</zeroOrMore>
<optional>
@@ -32,7 +31,6 @@
<choice>
<ref name="house-event-definition"/>
<ref name="basic-event-definition"/>
- <ref name="parameter-definition"/>
</choice>
</zeroOrMore>
</element>
@@ -51,7 +49,9 @@
</define>
<define name="reference">
- <ref name="Identifier"/>
+ <attribute name="name">
+ <ref name="Identifier"/>
+ </attribute>
</define>
<define name="NonEmptyString"> <!-- Texts without LF and other special chars. -->
@@ -116,66 +116,6 @@
<!-- III.1. CCF-Groups -->
<!-- ============================================================= -->
- <define name="CCF-group-definition">
- <element name="define-CCF-group">
- <ref name="name"/>
- <attribute name="model">
- <ref name="CCF-model"/>
- </attribute>
- <optional>
- <ref name="label"/>
- </optional>
- <optional>
- <ref name="attributes"/>
- </optional>
- <ref name="members"/>
- <ref name="distribution"/>
- <ref name="factors"/>
- </element>
- </define>
-
- <define name="members">
- <element name="members">
- <oneOrMore>
- <ref name="basic-event"/>
- </oneOrMore>
- </element>
- </define>
-
- <define name="factors">
- <choice>
- <element name="factors">
- <oneOrMore>
- <ref name="factor"/>
- </oneOrMore>
- </element>
- <ref name="factor"/>
- </choice>
- </define>
-
- <define name="factor">
- <element name="factor">
- <optional>
- <attribute name="level"> <data type="positiveInteger"/> </attribute>
- </optional>
- <ref name="expression"/>
- </element>
- </define>
-
- <define name="distribution">
- <element name="distribution">
- <ref name="expression"/>
- </element>
- </define>
-
- <define name="CCF-model">
- <choice>
- <value>beta-factor</value>
- <value>MGL</value>
- <value>alpha-factor</value>
- <value>phi-factor</value>
- </choice>
- </define>
<!-- ============================================================= -->
<!-- III.2. Substitutions -->
@@ -201,30 +141,7 @@
</optional>
<zeroOrMore>
<choice>
- <ref name="CCF-group-definition"/>
<ref name="event-definition"/>
- <ref name="component-definition"/>
- <ref name="parameter-definition"/>
- </choice>
- </zeroOrMore>
- </element>
- </define>
-
- <define name="component-definition">
- <element name="define-component">
- <ref name="name"/>
- <optional>
- <ref name="label"/>
- </optional>
- <optional>
- <ref name="attributes"/>
- </optional>
- <zeroOrMore>
- <choice>
- <ref name="CCF-group-definition"/>
- <ref name="event-definition"/>
- <ref name="component-definition"/>
- <ref name="parameter-definition"/>
</choice>
</zeroOrMore>
</element>
@@ -396,38 +313,6 @@
<!-- V.1. Definition of Parameters -->
<!-- ============================================================= -->
- <define name="parameter-definition">
- <element name="define-parameter">
- <ref name="name"/>
- <optional>
- <attribute name="unit">
- <ref name="units"/>
- </attribute>
- </optional>
- <optional>
- <ref name="label"/>
- </optional>
- <optional>
- <ref name="attributes"/>
- </optional>
- <ref name="expression"/>
- </element>
- </define>
-
- <define name="units">
- <choice>
- <value>bool</value>
- <value>int</value>
- <value>float</value>
- <value>hours</value>
- <value>hours-1</value>
- <value>years</value>
- <value>years-1</value>
- <value>fit</value>
- <value>demands</value>
- </choice>
- </define>
-
<!-- ============================================================= -->
<!-- V.2. Expressions -->
<!-- ============================================================= -->
@@ -439,7 +324,6 @@
<define name="expression">
<choice>
<ref name="constant"/>
- <ref name="parameter"/>
<ref name="built-in"/>
</choice>
</define>
@@ -452,26 +336,6 @@
</choice>
</define>
- <define name="parameter">
- <choice>
- <element name="parameter">
- <ref name="reference"/>
- <optional>
- <attribute name="unit">
- <ref name="units"/>
- </attribute>
- </optional>
- </element>
- <element name="system-mission-time">
- <optional>
- <attribute name="unit">
- <ref name="units"/>
- </attribute>
- </optional>
- </element>
- </choice>
- </define>
-
<define name="built-in">
<choice>
<ref name="exponential"/>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b763327..9457a3a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -55,28 +55,24 @@ set(SCRAM_CORE_SRC
"${CMAKE_CURRENT_SOURCE_DIR}/uncertainty_analysis.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/event_tree_analysis.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/reporter.cc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/serialization.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/initializer.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/risk_analysis.cc"
)
add_library(scramcore ${SCRAM_CORE_SRC})
if(INSTALL_LIBS)
- set_target_properties(scramcore
- PROPERTIES
- INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib"
- INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib"
- )
if(WIN32)
- install(
- TARGETS scramcore
- RUNTIME DESTINATION bin COMPONENT scram
- LIBRARY DESTINATION lib COMPONENT scram
- ARCHIVE DESTINATION lib COMPONENT scram
- )
+ install(TARGETS scramcore RUNTIME DESTINATION bin COMPONENT scram)
else()
+ set_target_properties(scramcore
+ PROPERTIES
+ INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib/scram"
+ INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib/scram"
+ )
install(
TARGETS scramcore
- DESTINATION lib
+ DESTINATION lib/scram
COMPONENT scram
)
endif()
diff --git a/src/ccf_group.cc b/src/ccf_group.cc
index a5ce4a8..de376ef 100644
--- a/src/ccf_group.cc
+++ b/src/ccf_group.cc
@@ -35,13 +35,13 @@ CcfEvent::CcfEvent(std::string name, const CcfGroup* ccf_group)
: BasicEvent(std::move(name), ccf_group->base_path(), ccf_group->role()),
ccf_group_(*ccf_group) {}
-void CcfGroup::AddMember(const BasicEventPtr& basic_event) {
+void CcfGroup::AddMember(BasicEvent* basic_event) {
if (distribution_ || factors_.empty() == false) {
throw IllegalOperation("No more members accepted. The distribution for " +
Element::name() +
" CCF group has already been defined.");
}
- if (ext::any_of(members_, [&basic_event](const BasicEventPtr& member) {
+ if (ext::any_of(members_, [&basic_event](BasicEvent* member) {
return member->name() == basic_event->name();
})) {
throw DuplicateArgumentError("Duplicate member " + basic_event->name() +
@@ -59,7 +59,7 @@ void CcfGroup::AddDistribution(Expression* distr) {
}
distribution_ = distr;
// Define probabilities of all basic events.
- for (const BasicEventPtr& member : members_)
+ for (BasicEvent* member : members_)
member->expression(distribution_);
}
@@ -138,7 +138,7 @@ std::string JoinNames(const std::vector<Gate*>& combination) {
void CcfGroup::ApplyModel() {
// Construct replacement proxy gates for member basic events.
std::vector<Gate*> proxy_gates;
- for (const BasicEventPtr& member : members_) {
+ for (BasicEvent* member : members_) {
auto new_gate = std::make_unique<Gate>(member->name(), member->base_path(),
member->role());
assert(member->id() == new_gate->id());
diff --git a/src/ccf_group.h b/src/ccf_group.h
index 5be9512..54f2e00 100644
--- a/src/ccf_group.h
+++ b/src/ccf_group.h
@@ -94,7 +94,7 @@ class CcfGroup : public Id, private boost::noncopyable {
virtual ~CcfGroup() = default;
/// @returns Members of the CCF group with original names as keys.
- const std::vector<BasicEventPtr>& members() const { return members_; }
+ const std::vector<BasicEvent*>& members() const { return members_; }
/// Adds a basic event into this CCF group.
/// This function asserts that each basic event has unique string id.
@@ -105,7 +105,7 @@ class CcfGroup : public Id, private boost::noncopyable {
/// @throws IllegalOperation The probability distribution or factors
/// for this CCF group are already defined.
/// No more members are accepted.
- void AddMember(const BasicEventPtr& basic_event);
+ void AddMember(BasicEvent* basic_event);
/// Adds the distribution that describes the probability of
/// basic events in this CCF group.
@@ -198,7 +198,7 @@ class CcfGroup : public Id, private boost::noncopyable {
int prev_level_ = 0; ///< To deduce optional levels from the previous level.
Expression* distribution_ = nullptr; ///< The group probability distribution.
- std::vector<BasicEventPtr> members_; ///< Members of CCF groups.
+ std::vector<BasicEvent*> members_; ///< Members of CCF groups.
ExpressionMap factors_; ///< CCF factors for models to get CCF probabilities.
/// Collection of expressions created specifically for this group.
std::vector<std::unique_ptr<Expression>> expressions_;
@@ -206,7 +206,7 @@ class CcfGroup : public Id, private boost::noncopyable {
std::vector<std::unique_ptr<CcfEvent>> ccf_events_;
};
-using CcfGroupPtr = std::shared_ptr<CcfGroup>; ///< Shared CCF groups.
+using CcfGroupPtr = std::unique_ptr<CcfGroup>; ///< Convenience alias.
/// Common cause failure model that assumes,
/// if common cause failure occurs,
diff --git a/src/element.cc b/src/element.cc
index eb075de..f98d83f 100644
--- a/src/element.cc
+++ b/src/element.cc
@@ -28,32 +28,37 @@
namespace scram {
namespace mef {
-Element::Element(std::string name) : kName_(std::move(name)) {
- if (kName_.empty())
- throw LogicError("The element name cannot be empty");
+Element::Element(std::string name) { Element::name(std::move(name)); }
- if (kName_.find('.') != std::string::npos)
+void Element::name(std::string name) {
+ if (name.empty())
+ throw LogicError("The element name cannot be empty");
+ if (name.find('.') != std::string::npos)
throw InvalidArgument("The element name is malformed.");
-}
-
-void Element::label(std::string new_label) {
- if (!label_.empty())
- throw LogicError("Trying to reset the label: " + label_);
- if (new_label.empty())
- throw LogicError("Trying to apply empty label");
-
- label_ = std::move(new_label);
+ name_ = std::move(name);
}
void Element::AddAttribute(Attribute attr) {
if (HasAttribute(attr.name)) {
throw DuplicateArgumentError(
- "Trying to overwrite an existing attribute {event: " + kName_ +
+ "Trying to overwrite an existing attribute {event: " + name_ +
", attr: " + attr.name + "} ");
}
attributes_.emplace_back(std::move(attr));
}
+void Element::SetAttribute(Attribute attr) {
+ auto it = boost::find_if(attributes_, [&attr](const Attribute& member) {
+ return attr.name == member.name;
+ });
+ if (it != attributes_.end()) {
+ it->value = std::move(attr.value);
+ it->type = std::move(attr.type);
+ } else {
+ attributes_.emplace_back(std::move(attr));
+ }
+}
+
bool Element::HasAttribute(const std::string& name) const {
return ext::any_of(attributes_, [&name](const Attribute& attr) {
return attr.name == name;
@@ -70,6 +75,16 @@ const Attribute& Element::GetAttribute(const std::string& name) const {
return *it;
}
+bool Element::RemoveAttribute(const std::string& name) {
+ auto it = boost::find_if(attributes_, [&name](const Attribute& attr) {
+ return attr.name == name;
+ });
+ if (it == attributes_.end())
+ return false;
+ attributes_.erase(it);
+ return true;
+}
+
Role::Role(RoleSpecifier role, std::string base_path)
: kBasePath_(std::move(base_path)),
kRole_(role) {
@@ -77,18 +92,18 @@ Role::Role(RoleSpecifier role, std::string base_path)
(kBasePath_.front() == '.' || kBasePath_.back() == '.')) {
throw InvalidArgument("Element reference base path is malformed.");
}
+ if (kRole_ == RoleSpecifier::kPrivate && kBasePath_.empty())
+ throw ValidationError("Elements cannot be private at model scope.");
}
Id::Id(std::string name, std::string base_path, RoleSpecifier role)
: Element(std::move(name)),
Role(role, std::move(base_path)),
- kId_(Role::role() == RoleSpecifier::kPublic
- ? Element::name()
- : Role::base_path() + "." + Element::name()) {
- if (Element::name().empty())
- throw LogicError("The name for an Id is empty!");
- if (Role::role() == RoleSpecifier::kPrivate && Role::base_path().empty())
- throw LogicError("The base path for a private element is empty.");
+ id_(MakeId(*this)) {}
+
+void Id::id(std::string name) {
+ Element::name(std::move(name));
+ id_ = MakeId(*this);
}
} // namespace mef
diff --git a/src/element.h b/src/element.h
index 8eba6c5..4058ae2 100644
--- a/src/element.h
+++ b/src/element.h
@@ -58,7 +58,7 @@ class Element {
explicit Element(std::string name);
/// @returns The original name.
- const std::string& name() const { return kName_; }
+ const std::string& name() const { return name_; }
/// @returns The empty or preset label.
/// @returns Empty string if the label has not been set.
@@ -66,11 +66,11 @@ class Element {
/// Sets the label.
///
- /// @param[in] new_label The label to be set.
- ///
- /// @throws LogicError The label is already set,
- /// or the new label is empty.
- void label(std::string new_label);
+ /// @param[in] label The label text to be set.
+ void label(std::string label) { label_ = std::move(label); }
+
+ /// @returns The current set of element attributes.
+ const std::vector<Attribute>& attributes() const { return attributes_; }
/// Adds an attribute to the attribute map.
///
@@ -83,6 +83,16 @@ class Element {
/// to existing attributes may get invalidated.
void AddAttribute(Attribute attr);
+ /// Sets an attribute to the attribute map.
+ /// If an attribute with the same name exits,
+ /// it gets overwritten.
+ ///
+ /// @param[in] attr An attribute of this element.
+ ///
+ /// @post Pointers or references
+ /// to existing attributes may get invalidated.
+ void SetAttribute(Attribute attr);
+
/// Checks if the element has a given attribute.
///
/// @param[in] name The identifying name of the attribute.
@@ -97,11 +107,26 @@ class Element {
/// @throws LogicError There is no such attribute.
const Attribute& GetAttribute(const std::string& name) const;
+ /// Removes the attribute of the element.
+ ///
+ /// @param[in] name The identifying name of the attribute.
+ ///
+ /// @returns false No such attribute to remove.
+ bool RemoveAttribute(const std::string& name);
+
protected:
~Element() = default;
+ /// Resets the element name.
+ ///
+ /// @param[in] name The local identifier name.
+ ///
+ /// @throws LogicError The name is required and empty.
+ /// @throws InvalidArgument The name is malformed.
+ void name(std::string name);
+
private:
- const std::string kName_; ///< The original name of the element.
+ std::string name_; ///< The original name of the element.
std::string label_; ///< The label text for the element.
/// Container of attributes ordered by insertion time.
@@ -116,6 +141,9 @@ class Element {
/// Table of elements with unique names.
///
/// @tparam T Value or (smart/raw) pointer type deriving from Element class.
+///
+/// @pre The element names are not modified
+/// while it is in the container.
template <typename T>
using ElementTable = boost::multi_index_container<
T, boost::multi_index::indexed_by<
@@ -139,6 +167,7 @@ class Role {
/// @param[in] base_path The series of containers to get this event.
///
/// @throws InvalidArgument The base path string is malformed.
+ /// @throws ValidationError Private element at model/global scope.
explicit Role(RoleSpecifier role = RoleSpecifier::kPublic,
std::string base_path = "");
@@ -158,13 +187,13 @@ class Role {
/// Computes the full path of an element.
///
-/// @tparam T Pointer to Element type deriving from Role.
+/// @tparam T Element type deriving from Role.
///
/// @param[in] element A valid element with a name and base path.
///
/// @returns A string representation of the full path.
template <typename T>
-std::string GetFullPath(const T& element) {
+std::string GetFullPath(const T* element) {
return element->base_path() + "." + element->name();
}
@@ -181,7 +210,17 @@ class Id : public Element, public Role {
RoleSpecifier role = RoleSpecifier::kPublic);
/// @returns The unique id that is set upon the construction of this element.
- const std::string& id() const { return kId_; }
+ const std::string& id() const { return id_; }
+
+ /// Resets the element ID.
+ ///
+ /// @param[in] name The new valid name for the element.
+ ///
+ /// @pre The element is not in any container keyed by its ID or name.
+ ///
+ /// @throws LogicError The name is empty.
+ /// @throws InvalidArgument The name is malformed.
+ void id(std::string name);
/// Produces unique name for the model element within the same type.
/// @{
@@ -197,12 +236,21 @@ class Id : public Element, public Role {
~Id() = default;
private:
- const std::string kId_; ///< Unique Id name of an element.
+ /// Creates an ID string for an element.
+ static std::string MakeId(const Id& element) {
+ return element.role() == RoleSpecifier::kPublic ? element.name()
+ : GetFullPath(&element);
+ }
+
+ std::string id_; ///< Unique Id name of an element.
};
/// Table of elements with unique ids.
///
/// @tparam T Value or (smart/raw) pointer type deriving from Id class.
+///
+/// @pre The element IDs are not modified
+/// while it is in the container.
template <typename T>
using IdTable = boost::multi_index_container<
T,
diff --git a/src/error.h b/src/error.h
index e371a6a..bb8ff2b 100644
--- a/src/error.h
+++ b/src/error.h
@@ -80,6 +80,11 @@ struct DuplicateArgumentError : public ValidationError {
using ValidationError::ValidationError;
};
+/// The error for undefined elements in a model.
+struct UndefinedElement : public ValidationError {
+ using ValidationError::ValidationError;
+};
+
/// Signals unacceptable cycles in invalid structures.
struct CycleError : public ValidationError {
using ValidationError::ValidationError;
diff --git a/src/event.cc b/src/event.cc
index 308afd1..b773895 100644
--- a/src/event.cc
+++ b/src/event.cc
@@ -44,6 +44,7 @@ void BasicEvent::Validate() const {
}
void Gate::Validate() const {
+ assert(formula_ && "The gate formula is missing.");
// Detect inhibit flavor.
if (formula_->type() != kAnd || !Element::HasAttribute("flavor") ||
Element::GetAttribute("flavor").value != "inhibit") {
@@ -101,6 +102,13 @@ void Formula::AddArgument(EventArg event_arg) {
event->usage(true);
}
+void Formula::RemoveArgument(EventArg event_arg) {
+ auto it = boost::find(event_args_, event_arg);
+ if (it == event_args_.end())
+ throw LogicError("The argument doesn't belong to this formula.");
+ event_args_.erase(it);
+}
+
void Formula::Validate() const {
switch (type_) {
case kAnd:
diff --git a/src/event.h b/src/event.h
index 98c4b83..0204693 100644
--- a/src/event.h
+++ b/src/event.h
@@ -59,9 +59,7 @@ class HouseEvent : public Event {
/// Sets the state for House event.
///
/// @param[in] constant False or True for the state of this house event.
- void state(bool constant) {
- state_ = constant;
- }
+ void state(bool constant) { state_ = constant; }
/// @returns The true or false state of this house event.
bool state() const { return state_; }
@@ -73,7 +71,6 @@ class HouseEvent : public Event {
};
class Gate;
-using GatePtr = std::shared_ptr<Gate>; ///< Shared gates in models.
/// Representation of a basic event in a fault tree.
class BasicEvent : public Event {
@@ -88,10 +85,8 @@ class BasicEvent : public Event {
/// Sets the expression of this basic event.
///
/// @param[in] expression The expression to describe this event.
- void expression(Expression* expression) {
- assert(!expression_ && "The basic event's expression is already set.");
- expression_ = expression;
- }
+ /// nullptr to remove unset the expression.
+ void expression(Expression* expression) { expression_ = expression; }
/// @returns The previously set expression for analysis purposes.
///
@@ -103,10 +98,10 @@ class BasicEvent : public Event {
/// @returns The mean probability of this basic event.
///
+ /// @pre The expression has been set.
+ ///
/// @note The user of this function should make sure
/// that the returned value is acceptable for calculations.
- ///
- /// @warning Undefined behavior if the expression is not set.
double p() const noexcept {
assert(expression_ && "The basic event's expression is not set.");
return expression_->value();
@@ -153,9 +148,12 @@ class BasicEvent : public Event {
std::unique_ptr<Gate> ccf_gate_;
};
-using EventPtr = std::shared_ptr<Event>; ///< Base shared pointer for events.
-using HouseEventPtr = std::shared_ptr<HouseEvent>; ///< Shared house events.
-using BasicEventPtr = std::shared_ptr<BasicEvent>; ///< Shared basic events.
+/// Convenience aliases for smart pointers @{
+using EventPtr = std::unique_ptr<Event>;
+using HouseEventPtr = std::unique_ptr<HouseEvent>;
+using BasicEventPtr = std::unique_ptr<BasicEvent>;
+using GatePtr = std::unique_ptr<Gate>;
+/// @}
class Formula; // To describe a gate's formula.
using FormulaPtr = std::unique_ptr<Formula>; ///< Non-shared gate formulas.
@@ -165,22 +163,38 @@ class Gate : public Event, public NodeMark {
public:
using Event::Event;
+ /// @returns true if the gate formula has been set.
+ bool HasFormula() const { return formula_ != nullptr; }
+
/// @returns The formula of this gate.
+ ///
+ /// @pre The gate has its formula initialized.
+ ///
/// @{
- const Formula& formula() const { return *formula_; }
- Formula& formula() { return *formula_; }
+ const Formula& formula() const {
+ assert(formula_ && "Gate formula is not set.");
+ return *formula_;
+ }
+ Formula& formula() {
+ return const_cast<Formula&>(static_cast<const Gate*>(this)->formula());
+ }
/// @}
/// Sets the formula of this gate.
///
- /// @param[in] formula Boolean formula of this gate.
- void formula(FormulaPtr formula) {
- assert(!formula_);
- formula_ = std::move(formula);
+ /// @param[in] formula The new Boolean formula of this gate.
+ ///
+ /// @returns The old formula.
+ FormulaPtr formula(FormulaPtr formula) {
+ assert(formula && "Cannot unset formula.");
+ formula_.swap(formula);
+ return formula;
}
/// Checks if a gate is initialized correctly.
///
+ /// @pre The gate formula is set.
+ ///
/// @throws ValidationError Errors in the gate's logic or setup.
void Validate() const;
@@ -264,6 +278,13 @@ class Formula : private boost::noncopyable {
formula_args_.emplace_back(std::move(formula));
}
+ /// Removes an event from the formula.
+ ///
+ /// @param[in] event_arg The argument event of this formula.
+ ///
+ /// @throws LogicError The argument does not belong to this formula.
+ void RemoveArgument(EventArg event_arg);
+
/// Checks if a formula is initialized correctly with the number of arguments.
///
/// @throws ValidationError Problems with the operator or arguments.
diff --git a/src/event_tree.cc b/src/event_tree.cc
index b4aa369..333b2ea 100644
--- a/src/event_tree.cc
+++ b/src/event_tree.cc
@@ -47,8 +47,8 @@ Fork::Fork(const FunctionalEvent& functional_event, std::vector<Path> paths)
}
}
-void EventTree::Add(SequencePtr sequence) {
- mef::AddElement<ValidationError>(std::move(sequence), &sequences_,
+void EventTree::Add(Sequence* sequence) {
+ mef::AddElement<ValidationError>(sequence, &sequences_,
"Duplicate sequence: ");
}
diff --git a/src/event_tree.h b/src/event_tree.h
index 6d370c4..76930a6 100644
--- a/src/event_tree.h
+++ b/src/event_tree.h
@@ -255,7 +255,7 @@ class Sequence : public Element, public Usage {
};
/// Sequences are defined in event trees but referenced in other constructs.
-using SequencePtr = std::shared_ptr<Sequence>;
+using SequencePtr = std::unique_ptr<Sequence>;
class EventTree; // Manages the order assignment to functional events.
@@ -391,7 +391,7 @@ class EventTree : public Element, public Usage, private boost::noncopyable {
/// @throws ValidationError The element is already in this container.
///
/// @{
- void Add(SequencePtr element);
+ void Add(Sequence* element);
void Add(FunctionalEventPtr element);
void Add(NamedBranchPtr element);
void Add(std::unique_ptr<Fork> element) {
@@ -404,7 +404,7 @@ class EventTree : public Element, public Usage, private boost::noncopyable {
/// Containers for unique event tree constructs defined in this event tree.
/// @{
- ElementTable<SequencePtr> sequences_;
+ ElementTable<Sequence*> sequences_;
ElementTable<FunctionalEventPtr> functional_events_;
ElementTable<NamedBranchPtr> branches_;
/// @}
diff --git a/src/ext/bits.h b/src/ext/bits.h
new file mode 100644
index 0000000..0ff2de3
--- /dev/null
+++ b/src/ext/bits.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file bits.h
+/// Helper constexpr functions to make bit operations explicit.
+
+#ifndef SCRAM_SRC_EXT_BITS_H_
+#define SCRAM_SRC_EXT_BITS_H_
+
+namespace ext {
+
+/// Tests the value of a bit.
+///
+/// @param[in] bits The array of bits.
+/// @param[in] index The index of the bit to test.
+///
+/// @returns The bit value in the given index.
+///
+/// @pre The index is in [0, size(bit-array)).
+constexpr bool test_bit(std::uint64_t bits, int index) noexcept {
+ return bits & (std::uint64_t(1) << index);
+}
+
+/// Counts the trailing zero bits.
+///
+/// @param[in] bits The positive number representing a bit array.
+///
+/// @returns The number of trailing 0 bits
+/// (the index of the first 1 bit).
+///
+/// @pre bits is not 0.
+constexpr int count_trailing_zero_bits(std::uint64_t bits) noexcept {
+#if defined(__GNUC__)
+ return __builtin_ctzl(bits);
+#else
+ int i = 0;
+ while (!test_bit(bits, i))
+ ++i;
+ return i;
+#endif
+}
+
+/// Helper function to map single bit integer/enum values into indices.
+///
+/// @param[in] bits The value represented as an array of bits.
+///
+/// @returns The index of the first 1 bit in the positive bit array.
+///
+/// @pre The bits value is not 0.
+constexpr int one_bit_index(std::uint64_t bits) noexcept {
+ return count_trailing_zero_bits(bits);
+}
+
+} // namespace ext
+
+#endif // SCRAM_SRC_EXT_BITS_H_
diff --git a/src/ext/find_iterator.h b/src/ext/find_iterator.h
index ef66d0d..0f039b2 100644
--- a/src/ext/find_iterator.h
+++ b/src/ext/find_iterator.h
@@ -37,8 +37,7 @@ class find_iterator : public Iterator {
/// @param[in] it The result of ``find`` call.
/// @param[in] it_end The sentinel iterator indicator ``not-found``.
find_iterator(Iterator&& it, const Iterator& it_end)
- : Iterator(std::forward<Iterator>(it)),
- found_(it != it_end) {}
+ : Iterator(std::move(it)), found_(*this != it_end) {}
/// @returns true if the iterator indicates that the item is found.
explicit operator bool() { return found_; }
@@ -51,15 +50,15 @@ class find_iterator : public Iterator {
/// with ``find_iterator`` adaptor.
///
/// @tparam T Container type supporting ``find()`` and ``end()`` calls.
-/// @tparam Ts The argument types to the ``find()`` call.
+/// @tparam Arg The argument type to the ``find()`` call.
///
/// @param[in] container The container to operate upon.
-/// @param[in] args Arguments to the ``find()`` function.
+/// @param[in] arg The argument to the ``find()`` function.
///
/// @returns find_iterator wrapping the resultant iterator.
-template <class T, typename... Ts>
-auto find(T&& container, Ts&&... args) {
- auto it = container.find(std::forward<Ts>(args)...);
+template <class T, typename Arg>
+auto find(T&& container, Arg&& arg) {
+ auto it = container.find(std::forward<Arg>(arg));
return find_iterator<decltype(it)>(std::move(it), container.end());
}
diff --git a/src/ext/multi_index.h b/src/ext/multi_index.h
new file mode 100644
index 0000000..3f7daca
--- /dev/null
+++ b/src/ext/multi_index.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file multi_index.h
+/// Helper functions to boost multi_index_container.
+
+#include <boost/multi_index_container.hpp>
+
+#ifndef SCRAM_SRC_EXT_MULTI_INDEX_H_
+#define SCRAM_SRC_EXT_MULTI_INDEX_H_
+
+namespace ext {
+
+/// Extracts (i.e., take, remove-return) a value from multi_index container.
+///
+/// @tparam T The value type in the container.
+/// @tparam Ts The rest of the multi_index container type parameters.
+///
+/// @param[in] it The iterator to the container.
+/// @param[in,out] container The container with the associated value.
+///
+/// @returns The extracted value.
+///
+/// @pre (it != container.end()).
+///
+/// @note This function breaks the contract of multi_index_container
+/// by modifying the value outside of the container.
+template <typename T, typename... Ts>
+T extract(typename boost::multi_index_container<T, Ts...>::iterator it,
+ boost::multi_index_container<T, Ts...>* container) noexcept {
+ assert(it != container->end());
+ T result = std::move(const_cast<T&>(*it)); // Theft, contract-violation.
+ container->erase(it); // Requires valid iterator but not value.
+ return result;
+}
+
+/// The same extraction but with an existing key-value.
+template <typename T, typename... Ts>
+T extract(const typename boost::multi_index_container<T, Ts...>::key_type& key,
+ boost::multi_index_container<T, Ts...>* container) noexcept {
+ return extract(container->find(key), container);
+}
+
+} // namespace ext
+
+#endif // SCRAM_SRC_EXT_MULTI_INDEX_H_
diff --git a/src/fault_tree.cc b/src/fault_tree.cc
index d3ce42d..0ed1019 100644
--- a/src/fault_tree.cc
+++ b/src/fault_tree.cc
@@ -30,28 +30,26 @@ Component::Component(std::string name, std::string base_path,
: Element(std::move(name)),
Role(role, std::move(base_path)) {}
-void Component::Add(const GatePtr& gate) {
- AddEvent(gate, &gates_);
-}
+void Component::Add(Gate* gate) { AddEvent(gate, &gates_); }
-void Component::Add(const BasicEventPtr& basic_event) {
+void Component::Add(BasicEvent* basic_event) {
AddEvent(basic_event, &basic_events_);
}
-void Component::Add(const HouseEventPtr& house_event) {
+void Component::Add(HouseEvent* house_event) {
AddEvent(house_event, &house_events_);
}
-void Component::Add(const ParameterPtr& parameter) {
+void Component::Add(Parameter* parameter) {
mef::AddElement<ValidationError>(parameter, ¶meters_,
"Duplicate parameter: ");
}
-void Component::Add(const CcfGroupPtr& ccf_group) {
+void Component::Add(CcfGroup* ccf_group) {
if (ccf_groups_.count(ccf_group->name())) {
throw ValidationError("Duplicate CCF group " + ccf_group->name());
}
- for (const BasicEventPtr& member : ccf_group->members()) {
+ for (BasicEvent* member : ccf_group->members()) {
const std::string& name = member->name();
if (gates_.count(name) || basic_events_.count(name) ||
house_events_.count(name)) {
@@ -71,16 +69,41 @@ void Component::Add(std::unique_ptr<Component> component) {
components_.insert(std::move(component));
}
-void Component::GatherGates(std::unordered_set<Gate*>* gates) {
- for (const GatePtr& gate : gates_)
- gates->insert(gate.get());
+namespace {
+
+/// Helper function to remove events from component containers.
+template <class T>
+void RemoveEvent(T* event, ElementTable<T*>* table) {
+ auto it = table->find(event->name());
+ if (it == table->end())
+ throw UndefinedElement("Event " + event->id() +
+ " is not in the component.");
+ if (*it != event)
+ throw UndefinedElement("Duplicate event " + event->id() +
+ " does not belong to the component.");
+ table->erase(it);
+}
+
+} // namespace
+
+void Component::Remove(HouseEvent* element) {
+ return RemoveEvent(element, &house_events_);
+}
+
+void Component::Remove(BasicEvent* element) {
+ return RemoveEvent(element, &basic_events_);
+}
+void Component::Remove(Gate* element) { return RemoveEvent(element, &gates_); }
+
+void Component::GatherGates(std::unordered_set<Gate*>* gates) {
+ gates->insert(gates_.begin(), gates_.end());
for (const ComponentPtr& component : components_)
component->GatherGates(gates);
}
-template <class Ptr, class Container>
-void Component::AddEvent(const Ptr& event, Container* container) {
+template <class T, class Container>
+void Component::AddEvent(T* event, Container* container) {
const std::string& name = event->name();
if (gates_.count(name) || basic_events_.count(name) ||
house_events_.count(name)) {
diff --git a/src/fault_tree.h b/src/fault_tree.h
index 8c7bf21..19a46c0 100644
--- a/src/fault_tree.h
+++ b/src/fault_tree.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014-2016 Olzhas Rakhimov
+ * Copyright (C) 2014-2017 Olzhas Rakhimov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -61,15 +61,15 @@ class Component : public Element, public Role, private boost::noncopyable {
/// @returns The container of component constructs of specific kind
/// with construct original names as keys.
/// @{
- const ElementTable<GatePtr>& gates() const { return gates_; }
- const ElementTable<BasicEventPtr>& basic_events() const {
+ const ElementTable<Gate*>& gates() const { return gates_; }
+ const ElementTable<BasicEvent*>& basic_events() const {
return basic_events_;
}
- const ElementTable<HouseEventPtr>& house_events() const {
+ const ElementTable<HouseEvent*>& house_events() const {
return house_events_;
}
- const ElementTable<ParameterPtr>& parameters() const { return parameters_; }
- const ElementTable<CcfGroupPtr>& ccf_groups() const { return ccf_groups_; }
+ const ElementTable<Parameter*>& parameters() const { return parameters_; }
+ const ElementTable<CcfGroup*>& ccf_groups() const { return ccf_groups_; }
const ElementTable<std::unique_ptr<Component>>& components() const {
return components_;
}
@@ -82,14 +82,26 @@ class Component : public Element, public Role, private boost::noncopyable {
/// @throws ValidationError The element is already in this container.
///
/// @{
- void Add(const GatePtr& element);
- void Add(const BasicEventPtr& element);
- void Add(const HouseEventPtr& element);
- void Add(const ParameterPtr& element);
- void Add(const CcfGroupPtr& element);
+ void Add(Gate* element);
+ void Add(BasicEvent* element);
+ void Add(HouseEvent* element);
+ void Add(Parameter* element);
+ void Add(CcfGroup* element);
void Add(std::unique_ptr<Component> element);
/// @}
+ /// Removes Event from the component container.
+ ///
+ /// @param[in] element An element defined in this model.
+ ///
+ /// @throws UndefinedElement The element doesn't belong to this container.
+ ///
+ /// @{
+ void Remove(HouseEvent* element);
+ void Remove(BasicEvent* element);
+ void Remove(Gate* element);
+ /// @}
+
protected:
/// Recursively traverses components
/// to gather gates relevant to the whole component.
@@ -101,23 +113,23 @@ class Component : public Element, public Role, private boost::noncopyable {
private:
/// Adds an event into this component container.
///
- /// @tparam Ptr The smart pointer type to the event.
+ /// @tparam T The event type.
/// @tparam Container Map with the event's original name as the key.
///
/// @param[in] event The event to be added to this component.
/// @param[in,out] container The destination container.
///
/// @throws ValidationError The event is already in this container.
- template <class Ptr, class Container>
- void AddEvent(const Ptr& event, Container* container);
+ template <class T, class Container>
+ void AddEvent(T* event, Container* container);
/// Container for component constructs with original names as keys.
/// @{
- ElementTable<GatePtr> gates_;
- ElementTable<BasicEventPtr> basic_events_;
- ElementTable<HouseEventPtr> house_events_;
- ElementTable<ParameterPtr> parameters_;
- ElementTable<CcfGroupPtr> ccf_groups_;
+ ElementTable<Gate*> gates_;
+ ElementTable<BasicEvent*> basic_events_;
+ ElementTable<HouseEvent*> house_events_;
+ ElementTable<Parameter*> parameters_;
+ ElementTable<CcfGroup*> ccf_groups_;
ElementTable<std::unique_ptr<Component>> components_;
/// @}
};
diff --git a/src/importance_analysis.cc b/src/importance_analysis.cc
index 7573702..30eef98 100644
--- a/src/importance_analysis.cc
+++ b/src/importance_analysis.cc
@@ -41,7 +41,9 @@ void ImportanceAnalysis::Analyze() noexcept {
this->basic_events();
std::vector<int> occurrences = this->occurrences();
- for (int i = 0; i < basic_events.size() && occurrences[i]; ++i) {
+ for (int i = 0; i < basic_events.size(); ++i) {
+ if (occurrences[i] == 0)
+ continue;
const mef::BasicEvent& event = *basic_events[i];
double p_var = event.p();
ImportanceFactors imp{};
diff --git a/src/initializer.cc b/src/initializer.cc
index 8265662..6a1ff34 100644
--- a/src/initializer.cc
+++ b/src/initializer.cc
@@ -72,6 +72,8 @@ RoleSpecifier GetRole(const std::string& s, RoleSpecifier parent_role) {
///
/// @param[in] xml_element XML element.
/// @param[out] element The object that needs attributes and label.
+///
+/// @throws ValidationError Invalid attribute setting.
void AttachLabelAndAttributes(const xmlpp::Element* xml_element,
Element* element) {
xmlpp::NodeSet labels = xml_element->find("./label");
@@ -80,6 +82,7 @@ void AttachLabelAndAttributes(const xmlpp::Element* xml_element,
const xmlpp::Element* label = XmlElement(labels.front());
const xmlpp::TextNode* text = label->get_child_text();
assert(text);
+ assert(element->label().empty() && "Resetting element label.");
element->label(GetContent(text));
}
@@ -87,20 +90,18 @@ void AttachLabelAndAttributes(const xmlpp::Element* xml_element,
if (attributes.empty())
return;
assert(attributes.size() == 1); // Only one big element 'attributes'.
- const xmlpp::Element* attribute = nullptr; // To report position.
- const xmlpp::Element* attributes_element = XmlElement(attributes.front());
-
- try {
- for (const xmlpp::Node* node : attributes_element->find("./attribute")) {
- attribute = XmlElement(node);
- Attribute attribute_struct = {GetAttributeValue(attribute, "name"),
- GetAttributeValue(attribute, "value"),
- GetAttributeValue(attribute, "type")};
+ for (const xmlpp::Node* node :
+ XmlElement(attributes.front())->find("./attribute")) {
+ const xmlpp::Element* attribute = XmlElement(node);
+ Attribute attribute_struct = {GetAttributeValue(attribute, "name"),
+ GetAttributeValue(attribute, "value"),
+ GetAttributeValue(attribute, "type")};
+ try {
element->AddAttribute(std::move(attribute_struct));
+ } catch (ValidationError& err) {
+ err.msg(GetLine(attribute) + err.msg());
+ throw;
}
- } catch(ValidationError& err) {
- err.msg(GetLine(attribute) + err.msg());
- throw;
}
}
@@ -226,33 +227,39 @@ void Initializer::Register(T&& element, const xmlpp::Element* xml_element) {
/// Specializations for element registrations.
/// @{
template <>
-GatePtr Initializer::Register(const xmlpp::Element* gate_node,
- const std::string& base_path,
- RoleSpecifier container_role) {
- GatePtr gate = ConstructElement<Gate>(gate_node, base_path, container_role);
- Register(gate, gate_node);
- tbd_.emplace_back(gate.get(), gate_node);
+Gate* Initializer::Register(const xmlpp::Element* gate_node,
+ const std::string& base_path,
+ RoleSpecifier container_role) {
+ GatePtr ptr = ConstructElement<Gate>(gate_node, base_path, container_role);
+ auto* gate = ptr.get();
+ Register(std::move(ptr), gate_node);
+ path_gates_.insert(gate);
+ tbd_.emplace_back(gate, gate_node);
return gate;
}
template <>
-BasicEventPtr Initializer::Register(const xmlpp::Element* event_node,
+BasicEvent* Initializer::Register(const xmlpp::Element* event_node,
const std::string& base_path,
RoleSpecifier container_role) {
- BasicEventPtr basic_event =
+ BasicEventPtr ptr =
ConstructElement<BasicEvent>(event_node, base_path, container_role);
- Register(basic_event, event_node);
- tbd_.emplace_back(basic_event.get(), event_node);
+ auto* basic_event = ptr.get();
+ Register(std::move(ptr), event_node);
+ path_basic_events_.insert(basic_event);
+ tbd_.emplace_back(basic_event, event_node);
return basic_event;
}
template <>
-HouseEventPtr Initializer::Register(const xmlpp::Element* event_node,
- const std::string& base_path,
- RoleSpecifier container_role) {
- HouseEventPtr house_event =
+HouseEvent* Initializer::Register(const xmlpp::Element* event_node,
+ const std::string& base_path,
+ RoleSpecifier container_role) {
+ HouseEventPtr ptr =
ConstructElement<HouseEvent>(event_node, base_path, container_role);
- Register(house_event, event_node);
+ auto* house_event = ptr.get();
+ Register(std::move(ptr), event_node);
+ path_house_events_.insert(house_event);
// Only Boolean constant.
xmlpp::NodeSet expression = event_node->find("./constant");
@@ -269,13 +276,15 @@ HouseEventPtr Initializer::Register(const xmlpp::Element* event_node,
}
template <>
-ParameterPtr Initializer::Register(const xmlpp::Element* param_node,
- const std::string& base_path,
- RoleSpecifier container_role) {
- ParameterPtr parameter =
+Parameter* Initializer::Register(const xmlpp::Element* param_node,
+ const std::string& base_path,
+ RoleSpecifier container_role) {
+ ParameterPtr ptr =
ConstructElement<Parameter>(param_node, base_path, container_role);
- Register(parameter, param_node);
- tbd_.emplace_back(parameter.get(), param_node);
+ auto* parameter = ptr.get();
+ Register(std::move(ptr), param_node);
+ path_parameters_.insert(parameter);
+ tbd_.emplace_back(parameter, param_node);
// Attach units.
std::string unit = GetAttributeValue(param_node, "unit");
@@ -288,10 +297,10 @@ ParameterPtr Initializer::Register(const xmlpp::Element* param_node,
}
template <>
-CcfGroupPtr Initializer::Register(const xmlpp::Element* ccf_node,
- const std::string& base_path,
- RoleSpecifier container_role) {
- auto ccf_group = [&]() -> CcfGroupPtr {
+CcfGroup* Initializer::Register(const xmlpp::Element* ccf_node,
+ const std::string& base_path,
+ RoleSpecifier container_role) {
+ auto ptr = [&]() -> CcfGroupPtr {
std::string model = GetAttributeValue(ccf_node, "model");
if (model == "beta-factor")
return ConstructElement<BetaFactorModel>(ccf_node, base_path,
@@ -305,24 +314,25 @@ CcfGroupPtr Initializer::Register(const xmlpp::Element* ccf_node,
return ConstructElement<PhiFactorModel>(ccf_node, base_path,
container_role);
}();
-
- Register(ccf_group, ccf_node);
+ auto* ccf_group = ptr.get();
+ Register(std::move(ptr), ccf_node);
xmlpp::NodeSet members = ccf_node->find("./members");
assert(members.size() == 1);
- ProcessCcfMembers(XmlElement(members[0]), ccf_group.get());
+ ProcessCcfMembers(XmlElement(members[0]), ccf_group);
- tbd_.emplace_back(ccf_group.get(), ccf_node);
+ tbd_.emplace_back(ccf_group, ccf_node);
return ccf_group;
}
template <>
-SequencePtr Initializer::Register(const xmlpp::Element* xml_node,
- const std::string& /*base_path*/,
- RoleSpecifier /*container_role*/) {
- SequencePtr sequence = ConstructElement<Sequence>(xml_node);
- Register(sequence, xml_node);
- tbd_.emplace_back(sequence.get(), xml_node);
+Sequence* Initializer::Register(const xmlpp::Element* xml_node,
+ const std::string& /*base_path*/,
+ RoleSpecifier /*container_role*/) {
+ SequencePtr ptr = ConstructElement<Sequence>(xml_node);
+ auto* sequence = ptr.get();
+ Register(std::move(ptr), xml_node);
+ tbd_.emplace_back(sequence, xml_node);
return sequence;
}
/// @}
@@ -344,7 +354,7 @@ void Initializer::ProcessInputFile(const std::string& xml_file) {
if (!model_) { // Create only one model for multiple files.
model_ = ConstructElement<Model>(XmlElement(root));
- model_->mission_time()->value(settings_.mission_time());
+ model_->mission_time().value(settings_.mission_time());
}
for (const xmlpp::Node* node : root->find("./define-initiating-event")) {
@@ -390,6 +400,7 @@ void Initializer::Define(const xmlpp::Element* gate_node, Gate* gate) {
// Assumes that there are no attributes and labels.
assert(formulas.size() == 1);
const xmlpp::Element* formula_node = XmlElement(formulas.front());
+ assert(!gate->HasFormula() && "Resetting gate formula");
gate->formula(GetFormula(formula_node, gate->base_path()));
try {
gate->Validate();
@@ -406,6 +417,7 @@ void Initializer::Define(const xmlpp::Element* event_node,
if (!expressions.empty()) {
const xmlpp::Element* expr_node = XmlElement(expressions.back());
+ assert(basic_event->HasExpression() == false && "Resetting expressions.");
basic_event->expression(GetExpression(expr_node, basic_event->base_path()));
}
}
@@ -643,17 +655,17 @@ FormulaPtr Initializer::GetFormula(const xmlpp::Element* formula_node,
try {
if (element_type == "event") { // Undefined type yet.
- formula->AddArgument(model_->GetEvent(name, base_path));
+ formula->AddArgument(GetEvent(name, base_path));
} else if (element_type == "gate") {
- formula->AddArgument(model_->GetGate(name, base_path));
+ formula->AddArgument(GetGate(name, base_path));
} else if (element_type == "basic-event") {
- formula->AddArgument(model_->GetBasicEvent(name, base_path));
+ formula->AddArgument(GetBasicEvent(name, base_path));
} else {
assert(element_type == "house-event");
- formula->AddArgument(model_->GetHouseEvent(name, base_path));
+ formula->AddArgument(GetHouseEvent(name, base_path));
}
} catch (std::out_of_range&) {
throw ValidationError(
@@ -829,7 +841,7 @@ struct Initializer::Extractor {
/// @param[in,out] init The host Initializer.
/// @param[in] expressions Accumulated argument expressions.
///
- /// @returns A shared pointer to the extracted expression.
+ /// @returns The extracted expression.
///
/// @throws std::out_of_range Not enough arguments in the args container.
template <class... Ts>
@@ -855,7 +867,7 @@ struct Initializer::Extractor<T, 0> {
///
/// @param[in] expressions All argument expressions for constructing T.
///
- /// @returns A shared pointer to the constructed expression.
+ /// @returns The constructed expression.
template <class... Ts>
std::unique_ptr<T> operator()(const xmlpp::NodeSet& /*args*/,
const std::string& /*base_path*/,
@@ -875,7 +887,7 @@ struct Initializer::Extractor<T, -1> {
/// @param[in] base_path Series of ancestor containers in the path with dots.
/// @param[in,out] init The host Initializer.
///
- /// @returns A shared pointer to the constructed expression.
+ /// @returns The constructed expression.
std::unique_ptr<T> operator()(const xmlpp::NodeSet& args,
const std::string& base_path,
Initializer* init) {
@@ -1119,7 +1131,7 @@ Expression* Initializer::GetParameter(const std::string& expr_type,
if (expr_type == "parameter") {
std::string name = GetAttributeValue(expr_element, "name");
try {
- Parameter* param = model_->GetParameter(name, base_path);
+ Parameter* param = GetParameter(name, base_path);
param->usage(true);
check_units(*param);
return param;
@@ -1129,8 +1141,8 @@ Expression* Initializer::GetParameter(const std::string& expr_type,
(base_path.empty() ? "" : " with base path " + base_path));
}
} else if (expr_type == "system-mission-time") {
- check_units(*model_->mission_time());
- return model_->mission_time().get();
+ check_units(model_->mission_time());
+ return &model_->mission_time();
}
return nullptr; // The expression is not a parameter.
}
@@ -1142,16 +1154,16 @@ void Initializer::ProcessCcfMembers(const xmlpp::Element* members_node,
assert("basic-event" == event_node->get_name());
std::string name = GetAttributeValue(event_node, "name");
- auto basic_event = std::make_shared<BasicEvent>(std::move(name),
+ auto basic_event = std::make_unique<BasicEvent>(std::move(name),
ccf_group->base_path(),
ccf_group->role());
try {
- ccf_group->AddMember(basic_event);
+ ccf_group->AddMember(basic_event.get());
} catch (DuplicateArgumentError& err) {
err.msg(GetLine(event_node) + err.msg());
throw;
}
- Register(basic_event, event_node);
+ Register(std::move(basic_event), event_node);
}
}
@@ -1174,6 +1186,87 @@ void Initializer::DefineCcfFactor(const xmlpp::Element* factor_node,
}
}
+Parameter* Initializer::GetParameter(const std::string& entity_reference,
+ const std::string& base_path) {
+ return GetEntity(entity_reference, base_path, model_->parameters(),
+ path_parameters_);
+}
+
+HouseEvent* Initializer::GetHouseEvent(const std::string& entity_reference,
+ const std::string& base_path) {
+ return GetEntity(entity_reference, base_path, model_->house_events(),
+ path_house_events_);
+}
+
+BasicEvent* Initializer::GetBasicEvent(const std::string& entity_reference,
+ const std::string& base_path) {
+ return GetEntity(entity_reference, base_path, model_->basic_events(),
+ path_basic_events_);
+}
+
+Gate* Initializer::GetGate(const std::string& entity_reference,
+ const std::string& base_path) {
+ return GetEntity(entity_reference, base_path, model_->gates(), path_gates_);
+}
+
+template <class P, class T>
+T* Initializer::GetEntity(const std::string& entity_reference,
+ const std::string& base_path,
+ const IdTable<P>& container,
+ const PathTable<T>& path_container) {
+ assert(!entity_reference.empty());
+ if (!base_path.empty()) { // Check the local scope.
+ if (auto it = ext::find(path_container,
+ base_path + "." + entity_reference))
+ return &**it;
+ }
+
+ auto at = [&entity_reference](const auto& reference_container) {
+ if (auto it = ext::find(reference_container, entity_reference))
+ return &**it;
+ throw std::out_of_range("The entity cannot be found.");
+ };
+
+ if (entity_reference.find('.') == std::string::npos) // Public entity.
+ return at(container);
+
+ return at(path_container); // Direct access.
+}
+
+/// Helper macro for Initializer::GetEvent event discovery.
+#define GET_EVENT(gates, basic_events, house_events, path_reference) \
+ do { \
+ if (auto it = ext::find(gates, path_reference)) \
+ return &**it; \
+ if (auto it = ext::find(basic_events, path_reference)) \
+ return &**it; \
+ if (auto it = ext::find(house_events, path_reference)) \
+ return &**it; \
+ } while (false)
+
+Formula::EventArg Initializer::GetEvent(const std::string& entity_reference,
+ const std::string& base_path) {
+ // Do not implement this in terms of
+ // GetGate, GetBasicEvent, or GetHouseEvent.
+ // The semantics for local lookup with the base type is different.
+ assert(!entity_reference.empty());
+ if (!base_path.empty()) { // Check the local scope.
+ std::string full_path = base_path + "." + entity_reference;
+ GET_EVENT(path_gates_, path_basic_events_, path_house_events_, full_path);
+ }
+
+ if (entity_reference.find('.') == std::string::npos) { // Public entity.
+ GET_EVENT(model_->gates(), model_->basic_events(), model_->house_events(),
+ entity_reference);
+ } else { // Direct access.
+ GET_EVENT(path_gates_, path_basic_events_, path_house_events_,
+ entity_reference);
+ }
+ throw std::out_of_range("The event cannot be bound.");
+}
+
+#undef GET_EVENT
+
void Initializer::ValidateInitialization() {
// Check if *all* gates have no cycles.
cycle::CheckCycle<Gate>(model_->gates(), "gate");
diff --git a/src/initializer.h b/src/initializer.h
index 44e1a75..cdc15e0 100644
--- a/src/initializer.h
+++ b/src/initializer.h
@@ -27,6 +27,9 @@
#include <utility>
#include <vector>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/global_fun.hpp>
+#include <boost/multi_index/hashed_index.hpp>
#include <boost/noncopyable.hpp>
#include <boost/variant.hpp>
#include <libxml++/libxml++.h>
@@ -79,9 +82,14 @@ class Initializer : private boost::noncopyable {
template <class... Ts>
using TbdContainer =
std::vector<std::pair<boost::variant<Ts*...>, const xmlpp::Element*>>;
-
- /// Expressions mapped to their extraction functions.
- static const ExtractorMap kExpressionExtractors_;
+ /// Container with full paths to elements.
+ ///
+ /// @tparam T The element type.
+ template <typename T>
+ using PathTable = boost::multi_index_container<
+ T*, boost::multi_index::indexed_by<
+ boost::multi_index::hashed_unique<boost::multi_index::global_fun<
+ const T*, std::string, &GetFullPath>>>>;
/// @tparam T Type of an expression.
/// @tparam N The number of arguments for the expression.
@@ -91,6 +99,9 @@ class Initializer : private boost::noncopyable {
template <class T, int N>
struct Extractor;
+ /// Expressions mapped to their extraction functions.
+ static const ExtractorMap kExpressionExtractors_;
+
/// Calls Extractor with an appropriate N to construct the expression.
///
/// @tparam T Type of an expression.
@@ -175,9 +186,8 @@ class Initializer : private boost::noncopyable {
///
/// @throws ValidationError Issues with the new element or registration.
template <class T>
- std::shared_ptr<T> Register(const xmlpp::Element* xml_node,
- const std::string& base_path,
- RoleSpecifier base_role);
+ T* Register(const xmlpp::Element* xml_node, const std::string& base_path,
+ RoleSpecifier base_role);
/// Adds additional data to element definition
/// after processing all the input files.
@@ -322,6 +332,49 @@ class Initializer : private boost::noncopyable {
/// or factors for specific CCF models.
void DefineCcfFactor(const xmlpp::Element* factor_node, CcfGroup* ccf_group);
+ /// Finds an entity (parameter, basic and house event, gate) from a reference.
+ /// The reference is case sensitive
+ /// and can contain an identifier, full path, or local path.
+ ///
+ /// @param[in] entity_reference Reference string to the entity.
+ /// @param[in] base_path The series of containers indicating the scope.
+ ///
+ /// @returns Pointer to the entity found by following the given reference.
+ ///
+ /// @throws std::out_of_range The entity cannot be found.
+ /// @{
+ Parameter* GetParameter(const std::string& entity_reference,
+ const std::string& base_path);
+ HouseEvent* GetHouseEvent(const std::string& entity_reference,
+ const std::string& base_path);
+ BasicEvent* GetBasicEvent(const std::string& entity_reference,
+ const std::string& base_path);
+ Gate* GetGate(const std::string& entity_reference,
+ const std::string& base_path);
+ Formula::EventArg GetEvent(const std::string& entity_reference,
+ const std::string& base_path);
+ /// @}
+
+ /// Generic helper function to find an entity from a reference.
+ /// The reference is case sensitive
+ /// and can contain an identifier, full path, or local path.
+ ///
+ /// @tparam P The pointer type managing the entity.
+ /// @tparam T The entity type.
+ ///
+ /// @param[in] entity_reference Reference string to the entity.
+ /// @param[in] base_path The series of containers indicating the scope.
+ /// @param[in] container Model's lookup container for entities with IDs.
+ /// @param[in] path_container The full path container for entities.
+ ///
+ /// @returns Pointer to the requested entity.
+ ///
+ /// @throws std::out_of_range The entity cannot be found.
+ template <class P, class T = typename P::element_type>
+ T* GetEntity(const std::string& entity_reference,
+ const std::string& base_path, const IdTable<P>& container,
+ const PathTable<T>& path_container);
+
/// Validates if the initialization of the analysis is successful.
///
/// @throws CycleError Model contains cycles.
@@ -404,6 +457,14 @@ class Initializer : private boost::noncopyable {
std::vector<std::pair<Expression*, const xmlpp::Element*>> expressions_;
/// Container for event tree links to check for cycles.
std::vector<Link*> links_;
+
+ /// Containers for reference resolution with paths.
+ /// @{
+ PathTable<Gate> path_gates_;
+ PathTable<BasicEvent> path_basic_events_;
+ PathTable<HouseEvent> path_house_events_;
+ PathTable<Parameter> path_parameters_;
+ /// @}
};
} // namespace mef
diff --git a/src/model.cc b/src/model.cc
index 9d5a2bd..0030ac0 100644
--- a/src/model.cc
+++ b/src/model.cc
@@ -22,6 +22,7 @@
#include "error.h"
#include "ext/find_iterator.h"
+#include "ext/multi_index.h"
namespace scram {
namespace mef {
@@ -30,7 +31,7 @@ const char Model::kDefaultName[] = "__unnamed-model__";
Model::Model(std::string name)
: Element(name.empty() ? kDefaultName : std::move(name)),
- mission_time_(std::make_shared<MissionTime>()) {}
+ mission_time_(std::make_unique<MissionTime>()) {}
void Model::Add(InitiatingEventPtr initiating_event) {
mef::AddElement<RedefinitionError>(std::move(initiating_event),
@@ -43,8 +44,8 @@ void Model::Add(EventTreePtr event_tree) {
"Redefinition of event tree: ");
}
-void Model::Add(const SequencePtr& sequence) {
- mef::AddElement<RedefinitionError>(sequence, &sequences_,
+void Model::Add(SequencePtr sequence) {
+ mef::AddElement<RedefinitionError>(std::move(sequence), &sequences_,
"Redefinition of sequence: ");
}
@@ -58,105 +59,85 @@ void Model::Add(FaultTreePtr fault_tree) {
"Redefinition of fault tree: ");
}
-void Model::Add(const ParameterPtr& parameter) {
- mef::AddElement<RedefinitionError>(parameter, ¶meters_,
+void Model::Add(ParameterPtr parameter) {
+ mef::AddElement<RedefinitionError>(std::move(parameter), ¶meters_,
"Redefinition of parameter: ");
}
-void Model::Add(const HouseEventPtr& house_event) {
- mef::AddElement<RedefinitionError>(house_event.get(), &events_,
- "Redefinition of event: ");
- house_events_.insert(house_event);
+void Model::CheckDuplicateEvent(const Event& event) {
+ const std::string& id = event.id();
+ if (gates_.count(id) || basic_events_.count(id) || house_events_.count(id))
+ throw RedefinitionError("Redefinition of event: " + id);
}
-void Model::Add(const BasicEventPtr& basic_event) {
- mef::AddElement<RedefinitionError>(basic_event.get(), &events_,
- "Redefinition of event: ");
- basic_events_.insert(basic_event);
+void Model::Add(HouseEventPtr house_event) {
+ CheckDuplicateEvent(*house_event);
+ house_events_.insert(std::move(house_event));
}
-void Model::Add(const GatePtr& gate) {
- mef::AddElement<RedefinitionError>(gate.get(), &events_,
- "Redefinition of event: ");
- gates_.insert(gate);
+void Model::Add(BasicEventPtr basic_event) {
+ CheckDuplicateEvent(*basic_event);
+ basic_events_.insert(std::move(basic_event));
}
-void Model::Add(const CcfGroupPtr& ccf_group) {
- mef::AddElement<RedefinitionError>(ccf_group, &ccf_groups_,
- "Redefinition of CCF group: ");
+void Model::Add(GatePtr gate) {
+ CheckDuplicateEvent(*gate);
+ gates_.insert(std::move(gate));
}
-Parameter* Model::GetParameter(const std::string& entity_reference,
- const std::string& base_path) {
- return GetEntity(entity_reference, base_path, parameters_);
+void Model::Add(CcfGroupPtr ccf_group) {
+ mef::AddElement<RedefinitionError>(std::move(ccf_group), &ccf_groups_,
+ "Redefinition of CCF group: ");
}
-HouseEvent* Model::GetHouseEvent(const std::string& entity_reference,
- const std::string& base_path) {
- return GetEntity(entity_reference, base_path, house_events_);
+Formula::EventArg Model::GetEvent(const std::string& id) {
+ if (auto it = ext::find(basic_events(), id))
+ return it->get();
+ if (auto it = ext::find(gates(), id))
+ return it->get();
+ if (auto it = ext::find(house_events(), id))
+ return it->get();
+ throw UndefinedElement("The event " + id + " is not in the model.");
}
-BasicEvent* Model::GetBasicEvent(const std::string& entity_reference,
- const std::string& base_path) {
- return GetEntity(entity_reference, base_path, basic_events_);
-}
+namespace {
-Gate* Model::GetGate(const std::string& entity_reference,
- const std::string& base_path) {
- return GetEntity(entity_reference, base_path, gates_);
+/// Helper function to remove events from containers.
+template <class T, class Table>
+std::unique_ptr<T> RemoveEvent(T* event, Table* table) {
+ auto it = table->find(event->id());
+ if (it == table->end())
+ throw UndefinedElement("Event " + event->id() + " is not in the model.");
+ if (it->get() != event)
+ throw UndefinedElement("Duplicate event " + event->id() +
+ " does not belong to the model.");
+ return ext::extract(it, table);
}
-template <class T>
-T* Model::GetEntity(const std::string& entity_reference,
- const std::string& base_path,
- const LookupTable<T>& container) {
- assert(!entity_reference.empty());
- if (!base_path.empty()) { // Check the local scope.
- if (auto it = ext::find(container.entities_by_path,
- base_path + "." + entity_reference))
- return it->get();
- }
-
- auto at = [&entity_reference](const auto& reference_container) {
- if (auto it = ext::find(reference_container, entity_reference))
- return it->get();
- throw std::out_of_range("The event cannot be found.");
- };
+} // namespace
- if (entity_reference.find('.') == std::string::npos) // Public entity.
- return at(container.entities_by_id);
-
- return at(container.entities_by_path); // Direct access.
+HouseEventPtr Model::Remove(HouseEvent* house_event) {
+ return RemoveEvent(house_event, &house_events_);
}
-/// Helper macro for Model::GetEvent event discovery.
-#define GET_EVENT(access, path_reference) \
- do { \
- if (auto it = ext::find(gates_.access, path_reference)) \
- return it->get(); \
- if (auto it = ext::find(basic_events_.access, path_reference)) \
- return it->get(); \
- if (auto it = ext::find(house_events_.access, path_reference)) \
- return it->get(); \
- } while (false)
-
-Formula::EventArg Model::GetEvent(const std::string& entity_reference,
- const std::string& base_path) {
- assert(!entity_reference.empty());
- if (!base_path.empty()) { // Check the local scope.
- std::string full_path = base_path + "." + entity_reference;
- GET_EVENT(entities_by_path, full_path);
- }
+BasicEventPtr Model::Remove(BasicEvent* basic_event) {
+ return RemoveEvent(basic_event, &basic_events_);
+}
- if (entity_reference.find('.') == std::string::npos) { // Public entity.
- GET_EVENT(entities_by_id, entity_reference);
- } else { // Direct access.
- GET_EVENT(entities_by_path, entity_reference);
- }
- throw std::out_of_range("The event cannot be bound.");
+GatePtr Model::Remove(Gate* gate) {
+ return RemoveEvent(gate, &gates_);
}
-#undef GET_EVENT
+FaultTreePtr Model::Remove(FaultTree* fault_tree) {
+ auto it = fault_trees_.find(fault_tree->name());
+ if (it == fault_trees_.end())
+ throw UndefinedElement("Fault tree " + fault_tree->name() +
+ " is not in the model.");
+ if (it->get() != fault_tree)
+ throw UndefinedElement("Duplicate fault tree " + fault_tree->name() +
+ " does not belong to the model.");
+ return ext::extract(it, &fault_trees_);
+}
} // namespace mef
} // namespace scram
diff --git a/src/model.h b/src/model.h
index 02c866f..87e0893 100644
--- a/src/model.h
+++ b/src/model.h
@@ -23,12 +23,8 @@
#include <memory>
#include <string>
-#include <type_traits>
#include <vector>
-#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/global_fun.hpp>
-#include <boost/multi_index/hashed_index.hpp>
#include <boost/noncopyable.hpp>
#include "ccf_group.h"
@@ -46,10 +42,10 @@ namespace mef {
/// This class represents a risk analysis model.
class Model : public Element, private boost::noncopyable {
public:
- /// @todo Only Model is allowed to have an optional name,
- /// while all other Elements require names.
- /// An empty name is an error for Element class invariants as well.
- /// This leads to a nasty magic string based optional name for a model.
+ /// Only Model is allowed to have an optional name,
+ /// while all other Elements require names.
+ /// An empty name is an error for Element class invariants as well.
+ /// This leads to a nasty magic string based optional name for a model.
static const char kDefaultName[];
/// Creates a model container.
@@ -59,6 +55,20 @@ class Model : public Element, private boost::noncopyable {
/// @throws InvalidArgument The name is malformed.
explicit Model(std::string name = "");
+ /// @returns true if the model name has not been set.
+ bool HasDefaultName() const { return Element::name() == kDefaultName; }
+
+ /// @returns The model name or an empty string for the optional name.
+ const std::string& GetOptionalName() const {
+ static const std::string empty_name("");
+ return HasDefaultName() ? empty_name : Element::name();
+ }
+
+ /// Sets the optional name of the model.
+ void SetOptionalName(std::string name = "") {
+ Element::name(name.empty() ? kDefaultName : std::move(name));
+ }
+
/// @returns The context to be used by test-event expressions
/// for event-tree walks.
///
@@ -75,19 +85,12 @@ class Model : public Element, private boost::noncopyable {
const ElementTable<SequencePtr>& sequences() const { return sequences_; }
const ElementTable<RulePtr>& rules() const { return rules_; }
const ElementTable<FaultTreePtr>& fault_trees() const { return fault_trees_; }
- const IdTable<ParameterPtr>& parameters() const {
- return parameters_.entities_by_id;
- }
- const std::shared_ptr<MissionTime>& mission_time() const {
- return mission_time_;
- }
- const IdTable<HouseEventPtr>& house_events() const {
- return house_events_.entities_by_id;
- }
- const IdTable<BasicEventPtr>& basic_events() const {
- return basic_events_.entities_by_id;
- }
- const IdTable<GatePtr>& gates() const { return gates_.entities_by_id; }
+ const IdTable<ParameterPtr>& parameters() const { return parameters_; }
+ const MissionTime& mission_time() const { return *mission_time_; }
+ MissionTime& mission_time() { return *mission_time_; }
+ const IdTable<HouseEventPtr>& house_events() const { return house_events_; }
+ const IdTable<BasicEventPtr>& basic_events() const { return basic_events_; }
+ const IdTable<GatePtr>& gates() const { return gates_; }
const IdTable<CcfGroupPtr>& ccf_groups() const { return ccf_groups_; }
/// @}
@@ -100,14 +103,14 @@ class Model : public Element, private boost::noncopyable {
/// @{
void Add(InitiatingEventPtr element);
void Add(EventTreePtr element);
- void Add(const SequencePtr& element);
+ void Add(SequencePtr element);
void Add(RulePtr element);
void Add(FaultTreePtr element);
- void Add(const ParameterPtr& element);
- void Add(const HouseEventPtr& element);
- void Add(const BasicEventPtr& element);
- void Add(const GatePtr& element);
- void Add(const CcfGroupPtr& element);
+ void Add(ParameterPtr element);
+ void Add(HouseEventPtr element);
+ void Add(BasicEventPtr element);
+ void Add(GatePtr element);
+ void Add(CcfGroupPtr element);
void Add(std::unique_ptr<Expression> element) {
expressions_.emplace_back(std::move(element));
}
@@ -116,79 +119,36 @@ class Model : public Element, private boost::noncopyable {
}
/// @}
- /// Finds an entity (parameter, basic and house event, gate) from a reference.
- /// The reference is case sensitive
- /// and can contain an identifier, full path, or local path.
+ /// Convenience function to retrieve an event with its ID.
///
- /// @param[in] entity_reference Reference string to the entity.
- /// @param[in] base_path The series of containers indicating the scope.
+ /// @param[in] id The valid ID string of the event.
///
- /// @returns Pointer to the entity found by following the given reference.
+ /// @returns The event with its type encoded in variant suitable for formulas.
///
- /// @throws std::out_of_range The entity cannot be found.
- /// @{
- Parameter* GetParameter(const std::string& entity_reference,
- const std::string& base_path);
- HouseEvent* GetHouseEvent(const std::string& entity_reference,
- const std::string& base_path);
- BasicEvent* GetBasicEvent(const std::string& entity_reference,
- const std::string& base_path);
- Gate* GetGate(const std::string& entity_reference,
- const std::string& base_path);
- Formula::EventArg
- GetEvent(const std::string& entity_reference, const std::string& base_path);
- /// @}
+ /// @throws UndefinedElement The event with the given ID is not in the model.
+ Formula::EventArg GetEvent(const std::string& id);
- private:
- /// Lookup containers for model entities with roles.
+ /// Removes MEF constructs from the model container.
///
- /// @tparam T Type of an entity with a role.
- template <typename T>
- struct LookupTable {
- static_assert(std::is_base_of<Role, T>::value, "Entity without a role!");
-
- /// Container with full path to elements.
- ///
- /// @tparam Ptr Pointer type to the T.
- template <typename Ptr>
- using PathTable = boost::multi_index_container<
- Ptr,
- boost::multi_index::indexed_by<
- boost::multi_index::hashed_unique<boost::multi_index::global_fun<
- const Ptr&, std::string, &GetFullPath>>>>;
-
- /// Adds an entry with an entity into lookup containers.
- ///
- /// @param[in] entity The candidate entity.
- ///
- /// @returns The result of insert call to IdTable.
- auto insert(const std::shared_ptr<T>& entity) {
- auto it = entities_by_id.insert(entity);
- if (it.second)
- entities_by_path.insert(entity);
- return it;
- }
-
- IdTable<std::shared_ptr<T>> entities_by_id; ///< Entity id as a key.
- PathTable<std::shared_ptr<T>> entities_by_path; ///< Full path as a key.
- };
-
- /// Generic helper function to find an entity from a reference.
- /// The reference is case sensitive
- /// and can contain an identifier, full path, or local path.
+ /// @param[in] element An element defined in this model.
///
- /// @tparam Container Map of name and entity pairs.
+ /// @returns The removed element.
///
- /// @param[in] entity_reference Reference string to the entity.
- /// @param[in] base_path The series of containers indicating the scope.
- /// @param[in] container Model's lookup container for entities.
+ /// @throws UndefinedElement The element cannot be found.
+ /// @{
+ HouseEventPtr Remove(HouseEvent* element);
+ BasicEventPtr Remove(BasicEvent* element);
+ GatePtr Remove(Gate* element);
+ FaultTreePtr Remove(FaultTree* element);
+ /// @}
+
+ private:
+ /// Checks if an event with the same id is already in the model.
///
- /// @returns Pointer to the requested entity.
+ /// @param[in] event The event to be tested for duplicate before insertion.
///
- /// @throws std::out_of_range The entity cannot be found.
- template <class T>
- T* GetEntity(const std::string& entity_reference,
- const std::string& base_path, const LookupTable<T>& container);
+ /// @throws RedefinitionError The element is already defined in the model.
+ void CheckDuplicateEvent(const Event& event);
/// A collection of defined constructs in the model.
/// @{
@@ -197,16 +157,15 @@ class Model : public Element, private boost::noncopyable {
ElementTable<SequencePtr> sequences_;
ElementTable<RulePtr> rules_;
ElementTable<FaultTreePtr> fault_trees_;
- LookupTable<Gate> gates_;
- LookupTable<HouseEvent> house_events_;
- LookupTable<BasicEvent> basic_events_;
- LookupTable<Parameter> parameters_;
- std::shared_ptr<MissionTime> mission_time_;
+ IdTable<GatePtr> gates_;
+ IdTable<HouseEventPtr> house_events_;
+ IdTable<BasicEventPtr> basic_events_;
+ IdTable<ParameterPtr> parameters_;
+ std::unique_ptr<MissionTime> mission_time_;
IdTable<CcfGroupPtr> ccf_groups_;
std::vector<std::unique_ptr<Expression>> expressions_;
std::vector<std::unique_ptr<Instruction>> instructions_;
/// @}
- IdTable<Event*> events_; ///< All events by ids.
Context context_; ///< The context to be used by test-event expressions.
};
diff --git a/src/parameter.h b/src/parameter.h
index 369b917..4d8321f 100644
--- a/src/parameter.h
+++ b/src/parameter.h
@@ -115,7 +115,7 @@ class Parameter : public Expression, public Id, public NodeMark, public Usage {
Expression* expression_ = nullptr; ///< Expression for this parameter.
};
-using ParameterPtr = std::shared_ptr<Parameter>; ///< Shared parameters.
+using ParameterPtr = std::unique_ptr<Parameter>; ///< Convenience alias.
} // namespace mef
} // namespace scram
diff --git a/src/reporter.cc b/src/reporter.cc
index 288ebf2..15cebf9 100644
--- a/src/reporter.cc
+++ b/src/reporter.cc
@@ -265,7 +265,7 @@ void Reporter::ReportSoftwareInformation(XmlStreamElement* information) {
void Reporter::ReportModelFeatures(const mef::Model& model,
XmlStreamElement* information) {
XmlStreamElement model_features = information->AddChild("model-features");
- if (model.name() != mef::Model::kDefaultName)
+ if (!model.HasDefaultName())
model_features.SetAttribute("name", model.name());
auto feature = [&model_features](const char* name, const auto& container) {
if (!container.empty())
diff --git a/src/risk_analysis.cc b/src/risk_analysis.cc
index 93855b5..2de63d6 100644
--- a/src/risk_analysis.cc
+++ b/src/risk_analysis.cc
@@ -30,10 +30,8 @@
namespace scram {
namespace core {
-RiskAnalysis::RiskAnalysis(std::shared_ptr<const mef::Model> model,
- const Settings& settings)
- : Analysis(settings),
- model_(std::move(model)) {}
+RiskAnalysis::RiskAnalysis(mef::Model* model, const Settings& settings)
+ : Analysis(settings), model_(model) {}
void RiskAnalysis::Analyze() noexcept {
assert(results_.empty() && "Rerunning the analysis.");
@@ -117,7 +115,7 @@ template <class Algorithm, class Calculator>
void RiskAnalysis::RunAnalysis(FaultTreeAnalyzer<Algorithm>* fta,
Result* result) noexcept {
auto pa = std::make_unique<ProbabilityAnalyzer<Calculator>>(
- fta, model_->mission_time().get());
+ fta, &model_->mission_time());
pa->Analyze();
if (Analysis::settings().importance_analysis()) {
auto ia = std::make_unique<ImportanceAnalyzer<Calculator>>(pa.get());
diff --git a/src/risk_analysis.h b/src/risk_analysis.h
index abef8ea..e268cd9 100644
--- a/src/risk_analysis.h
+++ b/src/risk_analysis.h
@@ -62,8 +62,13 @@ class RiskAnalysis : public Analysis {
/// @param[in] model An analysis model with fault trees, events, etc.
/// @param[in] settings Analysis settings for the given model.
- RiskAnalysis(std::shared_ptr<const mef::Model> model,
- const Settings& settings);
+ ///
+ /// @note The model is not const
+ /// because mission time and event-tree walk context are manipulated.
+ /// However, at the end of analysis, everything is reset.
+ ///
+ /// @todo Make the analysis work with a constant model.
+ RiskAnalysis(mef::Model* model, const Settings& settings);
/// @returns The model under analysis.
const mef::Model& model() const { return *model_; }
@@ -118,7 +123,7 @@ class RiskAnalysis : public Analysis {
template <class Algorithm, class Calculator>
void RunAnalysis(FaultTreeAnalyzer<Algorithm>* fta, Result* result) noexcept;
- std::shared_ptr<const mef::Model> model_; ///< The model with constructs.
+ mef::Model* model_; ///< The model with constructs.
std::vector<Result> results_; ///< The analysis result storage.
/// Event tree analysis of sequences.
/// @todo Incorporate into the main results container.
diff --git a/src/scram.cc b/src/scram.cc
index c6ba205..fdea1ce 100644
--- a/src/scram.cc
+++ b/src/scram.cc
@@ -32,6 +32,7 @@
#include "logger.h"
#include "reporter.h"
#include "risk_analysis.h"
+#include "serialization.h"
#include "settings.h"
#include "version.h"
@@ -79,6 +80,7 @@ po::options_description ConstructOptions() {
#ifndef NDEBUG
po::options_description debug("Debug Options");
debug.add_options()
+ ("serialize", "Serialize the input model without further analysis")
("preprocessor", "Stop analysis after the preprocessing step")
("print", "Print analysis results in a terminal friendly way")
("no-report", "Don't generate analysis report");
@@ -209,7 +211,7 @@ void ConstructSettings(const po::variables_map& vm,
/// @param[in] vm Variables map of program options.
///
/// @throws Error Exceptions specific to SCRAM.
-/// @throws boost::exception Boost errors.
+/// @throws boost::exception Boost errors with the variables map.
/// @throws std::exception All other problems.
void RunScram(const po::variables_map& vm) {
if (vm.count("verbosity")) {
@@ -240,25 +242,27 @@ void RunScram(const po::variables_map& vm) {
// Process input files
// into valid analysis containers and constructs.
// Throws if anything is invalid.
- auto init = std::make_unique<scram::mef::Initializer>(input_files, settings);
+ std::shared_ptr<scram::mef::Model> model =
+ scram::mef::Initializer(input_files, settings).model();
+#ifndef NDEBUG
+ if (vm.count("serialize"))
+ return Serialize(*model, std::cout);
+#endif
if (vm.count("validate"))
return; // Stop if only validation is requested.
// Initiate risk analysis with the given information.
- auto analysis =
- std::make_unique<scram::core::RiskAnalysis>(init->model(), settings);
- init.reset(); // Remove extra reference counts to shared objects.
-
- analysis->Analyze();
+ scram::core::RiskAnalysis analysis(model.get(), settings);
+ analysis.Analyze();
#ifndef NDEBUG
if (vm.count("no-report") || vm.count("preprocessor") || vm.count("print"))
return;
#endif
scram::Reporter reporter;
if (output_path.empty()) {
- reporter.Report(*analysis, std::cout);
+ reporter.Report(analysis, std::cout);
} else {
- reporter.Report(*analysis, output_path);
+ reporter.Report(analysis, output_path);
}
}
diff --git a/src/serialization.cc b/src/serialization.cc
new file mode 100644
index 0000000..65f9527
--- /dev/null
+++ b/src/serialization.cc
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file serialization.cc
+
+#include "serialization.h"
+
+#include <fstream>
+#include <ostream>
+
+#include "element.h"
+#include "event.h"
+#include "expression.h"
+#include "expression/constant.h"
+#include "expression/exponential.h"
+#include "fault_tree.h"
+#include "xml_stream.h"
+
+namespace scram {
+namespace mef {
+
+void Serialize(const Model& model, const std::string& file) {
+ std::ofstream of(file.c_str());
+ if (!of.good())
+ throw IOError(file + " : Cannot write the output file for serialization.");
+
+ Serialize(model, of);
+}
+
+namespace { // The serialization helper functions for each model construct.
+
+void SerializeLabelAndAttributes(const Element& element,
+ XmlStreamElement* xml_element) {
+ if (element.label().empty() == false)
+ xml_element->AddChild("label").AddText(element.label());
+ if (element.attributes().empty() == false) {
+ XmlStreamElement attributes_container = xml_element->AddChild("attributes");
+ for (const Attribute& attribute : element.attributes()) {
+ XmlStreamElement attribute_element =
+ attributes_container.AddChild("attribute");
+ assert(attribute.name.empty() == false);
+ attribute_element.SetAttribute("name", attribute.name);
+ assert(attribute.value.empty() == false);
+ attribute_element.SetAttribute("value", attribute.value);
+ if (attribute.type.empty() == false)
+ attribute_element.SetAttribute("type", attribute.type);
+ }
+ }
+}
+
+void SerializeElement(const Element& element, XmlStreamElement* xml_element) {
+ xml_element->SetAttribute("name", element.name());
+ SerializeLabelAndAttributes(element, xml_element);
+}
+
+void Serialize(const Formula& formula, XmlStreamElement* parent) {
+ assert(formula.formula_args().empty());
+ struct ArgStreamer {
+ void operator()(const Gate* gate) const {
+ xml->AddChild("gate").SetAttribute("name", gate->name());
+ }
+ void operator()(const BasicEvent* basic_event) const {
+ xml->AddChild("basic-event").SetAttribute("name", basic_event->name());
+ }
+ void operator()(const HouseEvent* house_event) const {
+ xml->AddChild("house-event").SetAttribute("name", house_event->name());
+ }
+
+ XmlStreamElement* xml;
+ };
+ if (formula.type() == kNull) {
+ assert(formula.event_args().size() == 1);
+ boost::apply_visitor(ArgStreamer{parent}, formula.event_args().front());
+ } else {
+ XmlStreamElement type_element = [&formula, &parent] {
+ switch (formula.type()) {
+ case kNot:
+ return parent->AddChild("not");
+ case kAnd:
+ return parent->AddChild("and");
+ case kOr:
+ return parent->AddChild("or");
+ case kNand:
+ return parent->AddChild("nand");
+ case kNor:
+ return parent->AddChild("nor");
+ case kXor:
+ return parent->AddChild("xor");
+ case kVote:
+ return [&formula, &parent] {
+ XmlStreamElement atleast = parent->AddChild("atleast");
+ atleast.SetAttribute("min", formula.vote_number());
+ return atleast;
+ }(); // Wrap NRVO into RVO for GCC.
+ default:
+ assert(false && "Unexpected formula");
+ }
+ }();
+ for (const Formula::EventArg& arg : formula.event_args())
+ boost::apply_visitor(ArgStreamer{&type_element}, arg);
+ }
+}
+
+void Serialize(const Gate& gate, XmlStreamElement* parent) {
+ assert(gate.role() == RoleSpecifier::kPublic);
+ XmlStreamElement gate_element = parent->AddChild("define-gate");
+ SerializeElement(gate, &gate_element);
+ Serialize(gate.formula(), &gate_element);
+}
+
+void Serialize(const FaultTree& fault_tree, XmlStreamElement* parent) {
+ assert(fault_tree.components().empty());
+ assert(fault_tree.role() == RoleSpecifier::kPublic);
+ XmlStreamElement ft_element = parent->AddChild("define-fault-tree");
+ SerializeElement(fault_tree, &ft_element);
+ for (Gate* gate : fault_tree.gates())
+ Serialize(*gate, &ft_element);
+}
+
+void Serialize(const Expression& expression, XmlStreamElement* parent) {
+ if (const auto* constant =
+ dynamic_cast<const ConstantExpression*>(&expression)) {
+ /// @todo Track the original value type of the constant expression.
+ parent->AddChild("float").SetAttribute(
+ "value", const_cast<ConstantExpression*>(constant)->value());
+ } else if (const auto* exponential =
+ dynamic_cast<const Exponential*>(&expression)) {
+ XmlStreamElement xml = parent->AddChild("exponential");
+ assert(exponential->args().size() == 2);
+ for (const Expression* arg : exponential->args())
+ Serialize(*arg, &xml);
+ } else {
+ assert(false && "Unsupported expression");
+ }
+}
+
+void Serialize(const BasicEvent& basic_event, XmlStreamElement* parent) {
+ assert(basic_event.role() == RoleSpecifier::kPublic);
+ XmlStreamElement be_element = parent->AddChild("define-basic-event");
+ SerializeElement(basic_event, &be_element);
+ if (basic_event.HasExpression())
+ Serialize(basic_event.expression(), &be_element);
+}
+
+void Serialize(const HouseEvent& house_event, XmlStreamElement* parent) {
+ assert(house_event.role() == RoleSpecifier::kPublic);
+ assert(&house_event != &HouseEvent::kTrue &&
+ &house_event != &HouseEvent::kFalse);
+ XmlStreamElement he_element = parent->AddChild("define-house-event");
+ SerializeElement(house_event, &he_element);
+ he_element.AddChild("constant")
+ .SetAttribute("value", house_event.state() ? "true" : "false");
+}
+
+} // namespace
+
+void Serialize(const Model& model, std::ostream& out) {
+ out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ XmlStreamElement root("opsa-mef", out);
+ if (!model.HasDefaultName())
+ root.SetAttribute("name", model.name());
+ SerializeLabelAndAttributes(model, &root);
+ /// @todo Implement serialization for the following unsupported constructs.
+ assert(model.ccf_groups().empty());
+ assert(model.parameters().empty());
+ assert(model.initiating_events().empty());
+ assert(model.event_trees().empty());
+ assert(model.sequences().empty());
+ assert(model.rules().empty());
+
+ for (const FaultTreePtr& fault_tree : model.fault_trees())
+ Serialize(*fault_tree, &root);
+
+ XmlStreamElement model_data = root.AddChild("model-data");
+ for (const BasicEventPtr& basic_event : model.basic_events())
+ Serialize(*basic_event, &model_data);
+ for (const HouseEventPtr& house_event : model.house_events())
+ Serialize(*house_event, &model_data);
+}
+
+} // namespace mef
+} // namespace scram
diff --git a/src/serialization.h b/src/serialization.h
new file mode 100644
index 0000000..af41dca
--- /dev/null
+++ b/src/serialization.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @file serialization.h
+/// The MEF Model serialization facilities.
+///
+/// @note This facility currently caters only models representable in the GUI.
+/// @todo Implement serialization for all MEF constructs.
+
+#ifndef SCRAM_SRC_SERIALIZATION_H_
+#define SCRAM_SRC_SERIALIZATION_H_
+
+#include <iosfwd>
+#include <string>
+
+#include "model.h"
+
+namespace scram {
+namespace mef {
+
+/// Serializes the model and its data into stream as XML.
+///
+/// @param[in] model Fully initialized and valid model.
+/// @param[in,out] out The stream for XML data.
+void Serialize(const Model& model, std::ostream& out);
+
+/// Convenience function for serialization into a file.
+///
+/// @param[in] model Fully initialized and valid model.
+/// @param[out] file The output destination.
+///
+/// @throws IOError The output file is not accessible.
+void Serialize(const Model& model, const std::string& file);
+
+} // namespace mef
+} // namespace scram
+
+#endif // SCRAM_SRC_SERIALIZATION_H_
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index dd3df16..febd9d3 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -28,6 +28,7 @@ set(SCRAM_CORE_TEST_SOURCE
"${CMAKE_CURRENT_SOURCE_DIR}/pdag_tests.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/initializer_tests.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/risk_analysis_tests.cc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/serialization_tests.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/bench_core_tests.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/bench_two_train_tests.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/bench_lift_tests.cc"
diff --git a/tests/bench_baobab1_tests.cc b/tests/bench_baobab1_tests.cc
index 51bed21..2cc8ab0 100644
--- a/tests/bench_baobab1_tests.cc
+++ b/tests/bench_baobab1_tests.cc
@@ -59,6 +59,24 @@ TEST_P(RiskAnalysisTest, Baobab1L8) {
EXPECT_EQ(distr, ProductDistribution());
}
+TEST_P(RiskAnalysisTest, Baobab1L4Importance) {
+ std::vector<std::string> input_files = {
+ "./share/scram/input/Baobab/baobab1.xml",
+ "./share/scram/input/Baobab/baobab1-basic-events.xml"};
+ settings.limit_order(4).importance_analysis(true);
+ ASSERT_NO_THROW(ProcessInputFiles(input_files));
+ ASSERT_NO_THROW(analysis->Analyze());
+ EXPECT_EQ(72, products().size());
+ EXPECT_EQ(40, analysis->results()
+ .front()
+ .fault_tree_analysis->products()
+ .product_events()
+ .size());
+ EXPECT_EQ(
+ 40,
+ analysis->results().front().importance_analysis->importance().size());
+}
+
} // namespace test
} // namespace core
} // namespace scram
diff --git a/tests/ccf_group_tests.cc b/tests/ccf_group_tests.cc
index f72874b..dde71ec 100644
--- a/tests/ccf_group_tests.cc
+++ b/tests/ccf_group_tests.cc
@@ -28,24 +28,24 @@ namespace test {
TEST(CcfGroupTest, AddMemberRepeated) {
BetaFactorModel ccf_group("general");
- BasicEventPtr member(new BasicEvent("id"));
- ASSERT_NO_THROW(ccf_group.AddMember(member));
- EXPECT_THROW(ccf_group.AddMember(member), ValidationError);
+ BasicEvent member("id");
+ ASSERT_NO_THROW(ccf_group.AddMember(&member));
+ EXPECT_THROW(ccf_group.AddMember(&member), ValidationError);
}
TEST(CcfGroupTest, AddMemberAfterDistribution) {
BetaFactorModel ccf_group("general");
- BasicEventPtr member_one(new BasicEvent("id"));
- ASSERT_NO_THROW(ccf_group.AddMember(member_one));
+ BasicEvent member_one("id");
+ ASSERT_NO_THROW(ccf_group.AddMember(&member_one));
- BasicEventPtr member_two(new BasicEvent("two"));
- EXPECT_NO_THROW(ccf_group.AddMember(member_two));
+ BasicEvent member_two("two");
+ EXPECT_NO_THROW(ccf_group.AddMember(&member_two));
ASSERT_NO_THROW(ccf_group.AddDistribution(&ConstantExpression::kOne));
- BasicEventPtr member_three(new BasicEvent("three"));
- EXPECT_THROW(ccf_group.AddMember(member_three), IllegalOperation);
+ BasicEvent member_three("three");
+ EXPECT_THROW(ccf_group.AddMember(&member_three), IllegalOperation);
}
} // namespace test
diff --git a/tests/element_tests.cc b/tests/element_tests.cc
index e2a9349..c124d93 100644
--- a/tests/element_tests.cc
+++ b/tests/element_tests.cc
@@ -56,12 +56,14 @@ TEST(ElementTest, Name) {
TEST(ElementTest, Label) {
NamedElement el("name");
EXPECT_EQ("", el.label());
- EXPECT_THROW(el.label(""), LogicError);
+ EXPECT_NO_THROW(el.label(""));
ASSERT_NO_THROW(el.label("label"));
- EXPECT_THROW(el.label("new_label"), LogicError);
+ EXPECT_EQ("label", el.label());
+ EXPECT_NO_THROW(el.label("new_label"));
+ EXPECT_NO_THROW(el.label(""));
}
-TEST(ElementTest, Attribute) {
+TEST(ElementTest, AddAttribute) {
NamedElement el("name");
Attribute attr;
attr.name = "impact";
@@ -72,6 +74,46 @@ TEST(ElementTest, Attribute) {
EXPECT_THROW(el.AddAttribute(attr), DuplicateArgumentError);
ASSERT_TRUE(el.HasAttribute(attr.name));
ASSERT_NO_THROW(el.GetAttribute(attr.name));
+ EXPECT_EQ(attr.value, el.GetAttribute(attr.name).value);
+ EXPECT_EQ(attr.name, el.GetAttribute(attr.name).name);
+}
+
+TEST(ElementTest, SetAttribute) {
+ NamedElement el("name");
+ Attribute attr;
+ attr.name = "impact";
+ attr.value = "0.1";
+ attr.type = "float";
+ EXPECT_THROW(el.GetAttribute(attr.name), LogicError);
+ ASSERT_NO_THROW(el.SetAttribute(attr));
+ EXPECT_THROW(el.AddAttribute(attr), DuplicateArgumentError);
+ ASSERT_TRUE(el.HasAttribute(attr.name));
+ ASSERT_NO_THROW(el.GetAttribute(attr.name));
+ EXPECT_EQ(attr.value, el.GetAttribute(attr.name).value);
+ EXPECT_EQ(attr.name, el.GetAttribute(attr.name).name);
+
+ attr.value = "0.2";
+ ASSERT_NO_THROW(el.SetAttribute(attr));
+ EXPECT_EQ(1, el.attributes().size());
+ ASSERT_NO_THROW(el.GetAttribute(attr.name));
+ EXPECT_EQ(attr.value, el.GetAttribute(attr.name).value);
+}
+
+TEST(ElementTest, RemoveAttribute) {
+ NamedElement el("name");
+ Attribute attr;
+ attr.name = "impact";
+ attr.value = "0.1";
+ attr.type = "float";
+
+ EXPECT_FALSE(el.HasAttribute(attr.name));
+ EXPECT_TRUE(el.attributes().empty());
+ EXPECT_FALSE(el.RemoveAttribute(attr.name));
+
+ ASSERT_NO_THROW(el.AddAttribute(attr));
+ EXPECT_TRUE(el.RemoveAttribute(attr.name));
+ EXPECT_FALSE(el.HasAttribute(attr.name));
+ EXPECT_TRUE(el.attributes().empty());
}
namespace {
@@ -87,6 +129,9 @@ TEST(ElementTest, Role) {
EXPECT_THROW(TestRole(RoleSpecifier::kPublic, ".ref"), InvalidArgument);
EXPECT_THROW(TestRole(RoleSpecifier::kPublic, "ref."), InvalidArgument);
EXPECT_NO_THROW(TestRole(RoleSpecifier::kPublic, "ref.name"));
+
+ EXPECT_THROW(TestRole(RoleSpecifier::kPrivate, ""), ValidationError);
+ EXPECT_NO_THROW(TestRole(RoleSpecifier::kPublic, ""));
}
namespace {
@@ -101,10 +146,24 @@ class NameId : public Id {
TEST(ElementTest, Id) {
EXPECT_THROW(NameId(""), LogicError);
EXPECT_NO_THROW(NameId("name"));
- EXPECT_THROW(NameId("name", "", RoleSpecifier::kPrivate), LogicError);
+ EXPECT_THROW(NameId("name", "", RoleSpecifier::kPrivate), ValidationError);
+
NameId id_public("name");
+ EXPECT_EQ(id_public.id(), id_public.name());
+
NameId id_private("name", "path", RoleSpecifier::kPrivate);
+ EXPECT_EQ("path.name", id_private.id());
+ EXPECT_NE(id_private.id(), id_private.name());
+
EXPECT_NE(id_public.id(), id_private.id());
+
+ // Reset.
+ id_public.id("id");
+ EXPECT_EQ("id", id_public.id());
+ EXPECT_EQ("id", id_public.name());
+ id_private.id("id");
+ EXPECT_EQ("path.id", id_private.id());
+ EXPECT_EQ("id", id_private.name());
}
} // namespace test
diff --git a/tests/event_tests.cc b/tests/event_tests.cc
index 607d248..27b1bbf 100644
--- a/tests/event_tests.cc
+++ b/tests/event_tests.cc
@@ -21,6 +21,7 @@
#include "cycle.h"
#include "error.h"
+#include "expression/constant.h"
namespace scram {
namespace mef {
@@ -32,6 +33,39 @@ TEST(EventTest, Id) {
EXPECT_EQ(event.id(), "event_name");
}
+TEST(BasicEventTest, ExpressionReset) {
+ BasicEvent event("event");
+ EXPECT_FALSE(event.HasExpression());
+ ConstantExpression p_init(0.5);
+ event.expression(&p_init);
+ ASSERT_TRUE(event.HasExpression());
+ EXPECT_EQ(0.5, event.p());
+
+ ConstantExpression p_change(0.1);
+ event.expression(&p_change);
+ ASSERT_TRUE(event.HasExpression());
+ EXPECT_EQ(0.1, event.p());
+}
+
+TEST(BasicEventTest, Validate) {
+ BasicEvent event("event");
+ EXPECT_FALSE(event.HasExpression());
+ ConstantExpression p_valid(0.5);
+ event.expression(&p_valid);
+ ASSERT_TRUE(event.HasExpression());
+ EXPECT_NO_THROW(event.Validate());
+
+ ConstantExpression p_negative(-0.1);
+ event.expression(&p_negative);
+ ASSERT_TRUE(event.HasExpression());
+ EXPECT_THROW(event.Validate(), ValidationError);
+
+ ConstantExpression p_large(1.1);
+ event.expression(&p_large);
+ ASSERT_TRUE(event.HasExpression());
+ EXPECT_THROW(event.Validate(), ValidationError);
+}
+
TEST(FormulaTest, VoteNumber) {
FormulaPtr top(new Formula(kAnd));
EXPECT_EQ(kAnd, top->type());
@@ -68,6 +102,10 @@ TEST(FormulaTest, EventArguments) {
EXPECT_NO_THROW(top->AddArgument(&second_child));
EXPECT_EQ(2, top->event_args().size());
EXPECT_EQ(&second_child, boost::get<BasicEvent*>(top->event_args().back()));
+
+ EXPECT_NO_THROW(top->RemoveArgument(&first_child));
+ EXPECT_EQ(1, top->num_args());
+ EXPECT_THROW(top->RemoveArgument(&first_child), LogicError);
}
TEST(FormulaTest, FormulaArguments) {
diff --git a/tests/fault_tree_tests.cc b/tests/fault_tree_tests.cc
index 413d967..7f9bd6b 100644
--- a/tests/fault_tree_tests.cc
+++ b/tests/fault_tree_tests.cc
@@ -27,52 +27,52 @@ namespace test {
TEST(FaultTreeTest, AddGate) {
FaultTree ft("never_fail");
- GatePtr gate(new Gate("Golden"));
- EXPECT_NO_THROW(ft.Add(gate));
- EXPECT_THROW(ft.Add(gate), ValidationError); // Trying to re-add.
+ Gate gate("Golden");
+ EXPECT_NO_THROW(ft.Add(&gate));
+ EXPECT_THROW(ft.Add(&gate), ValidationError); // Trying to re-add.
- GatePtr gate_two(new Gate("Iron"));
- EXPECT_NO_THROW(ft.Add(gate_two)); // No parent.
+ Gate gate_two("Iron");
+ EXPECT_NO_THROW(ft.Add(&gate_two)); // No parent.
}
TEST(FaultTreeTest, AddBasicEvent) {
FaultTree ft("never_fail");
- BasicEventPtr event(new BasicEvent("Golden"));
- EXPECT_NO_THROW(ft.Add(event));
- EXPECT_THROW(ft.Add(event), ValidationError); // Trying to re-add.
+ BasicEvent event("Golden");
+ EXPECT_NO_THROW(ft.Add(&event));
+ EXPECT_THROW(ft.Add(&event), ValidationError); // Trying to re-add.
- BasicEventPtr event_two(new BasicEvent("Iron"));
- EXPECT_NO_THROW(ft.Add(event_two)); // No parent.
+ BasicEvent event_two("Iron");
+ EXPECT_NO_THROW(ft.Add(&event_two)); // No parent.
}
TEST(FaultTreeTest, AddHouseEvent) {
FaultTree ft("never_fail");
- HouseEventPtr event(new HouseEvent("Golden"));
- EXPECT_NO_THROW(ft.Add(event));
- EXPECT_THROW(ft.Add(event), ValidationError); // Trying to re-add.
+ HouseEvent event("Golden");
+ EXPECT_NO_THROW(ft.Add(&event));
+ EXPECT_THROW(ft.Add(&event), ValidationError); // Trying to re-add.
- HouseEventPtr event_two(new HouseEvent("Iron"));
- EXPECT_NO_THROW(ft.Add(event_two)); // No parent.
+ HouseEvent event_two("Iron");
+ EXPECT_NO_THROW(ft.Add(&event_two)); // No parent.
}
TEST(FaultTreeTest, AddCcfGroup) {
FaultTree ft("never_fail");
- CcfGroupPtr group(new BetaFactorModel("Golden"));
- EXPECT_NO_THROW(ft.Add(group));
- EXPECT_THROW(ft.Add(group), ValidationError); // Trying to re-add.
+ BetaFactorModel group("Golden");
+ EXPECT_NO_THROW(ft.Add(&group));
+ EXPECT_THROW(ft.Add(&group), ValidationError); // Trying to re-add.
- CcfGroupPtr group_two(new BetaFactorModel("Iron"));
- EXPECT_NO_THROW(ft.Add(group_two));
+ BetaFactorModel group_two("Iron");
+ EXPECT_NO_THROW(ft.Add(&group_two));
}
TEST(FaultTreeTest, AddParameter) {
FaultTree ft("never_fail");
- ParameterPtr parameter(new Parameter("Golden"));
- EXPECT_NO_THROW(ft.Add(parameter));
- EXPECT_THROW(ft.Add(parameter), ValidationError);
+ Parameter parameter("Golden");
+ EXPECT_NO_THROW(ft.Add(¶meter));
+ EXPECT_THROW(ft.Add(¶meter), ValidationError);
- ParameterPtr parameter_two(new Parameter("Iron"));
- EXPECT_NO_THROW(ft.Add(parameter_two));
+ Parameter parameter_two("Iron");
+ EXPECT_NO_THROW(ft.Add(¶meter_two));
}
} // namespace test
diff --git a/tests/initializer_tests.cc b/tests/initializer_tests.cc
index 5ed3145..7df43f9 100644
--- a/tests/initializer_tests.cc
+++ b/tests/initializer_tests.cc
@@ -232,6 +232,7 @@ TEST(InitializerTest, IncorrectFtaInputs) {
const char* incorrect_inputs[] = {
"int_overflow.xml",
"invalid_probability.xml",
+ "private_at_model_scope.xml",
"doubly_defined_gate.xml",
"doubly_defined_house.xml",
"doubly_defined_basic.xml",
diff --git a/tests/input/fta/private_at_model_scope.xml b/tests/input/fta/private_at_model_scope.xml
new file mode 100644
index 0000000..93241ba
--- /dev/null
+++ b/tests/input/fta/private_at_model_scope.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<opsa-mef name="PrivateModel">
+ <model-data>
+ <define-basic-event name="ValveOne" role="private"/>
+ </model-data>
+</opsa-mef>
diff --git a/tests/performance_tests.h b/tests/performance_tests.h
index a73630b..2d0d66e 100644
--- a/tests/performance_tests.h
+++ b/tests/performance_tests.h
@@ -44,8 +44,8 @@ class PerformanceTest : public ::testing::Test {
// Convenient function to manage analysis of one model in input files.
void Analyze(const std::vector<std::string>& input_files) {
{
- mef::Initializer init(input_files, settings);
- analysis = std::make_unique<RiskAnalysis>(init.model(), settings);
+ model = mef::Initializer(input_files, settings).model();
+ analysis = std::make_unique<RiskAnalysis>(model.get(), settings);
}
analysis->Analyze();
}
@@ -76,6 +76,7 @@ class PerformanceTest : public ::testing::Test {
return analysis->results().front().probability_analysis->analysis_time();
}
+ std::shared_ptr<mef::Model> model;
std::unique_ptr<RiskAnalysis> analysis;
Settings settings;
double delta; // The range indicator for values.
diff --git a/tests/risk_analysis_tests.cc b/tests/risk_analysis_tests.cc
index 2e3640c..b5e0d33 100644
--- a/tests/risk_analysis_tests.cc
+++ b/tests/risk_analysis_tests.cc
@@ -50,7 +50,7 @@ void RiskAnalysisTest::ProcessInputFiles(
const std::vector<std::string>& input_files) {
mef::Initializer init(input_files, settings);
model = init.model();
- analysis = std::make_unique<RiskAnalysis>(model, settings);
+ analysis = std::make_unique<RiskAnalysis>(model.get(), settings);
result_ = Result();
}
@@ -135,21 +135,21 @@ TEST_F(RiskAnalysisTest, ProcessInput) {
EXPECT_EQ(1, basic_events().count("ValveTwo"));
ASSERT_TRUE(gates().count("TopEvent"));
- mef::GatePtr top = *gates().find("TopEvent");
+ mef::Gate* top = gates().find("TopEvent")->get();
EXPECT_EQ("TopEvent", top->id());
ASSERT_NO_THROW(top->formula().type());
EXPECT_EQ(mef::kAnd, top->formula().type());
EXPECT_EQ(2, top->formula().event_args().size());
ASSERT_TRUE(gates().count("TrainOne"));
- mef::GatePtr inter = *gates().find("TrainOne");
+ mef::Gate* inter = gates().find("TrainOne")->get();
EXPECT_EQ("TrainOne", inter->id());
ASSERT_NO_THROW(inter->formula().type());
EXPECT_EQ(mef::kOr, inter->formula().type());
EXPECT_EQ(2, inter->formula().event_args().size());
ASSERT_TRUE(basic_events().count("ValveOne"));
- mef::BasicEventPtr primary = *basic_events().find("ValveOne");
+ mef::BasicEvent* primary = basic_events().find("ValveOne")->get();
EXPECT_EQ("ValveOne", primary->id());
}
@@ -165,10 +165,10 @@ TEST_F(RiskAnalysisTest, PopulateProbabilities) {
ASSERT_EQ(1, basic_events().count("ValveOne"));
ASSERT_EQ(1, basic_events().count("ValveTwo"));
- mef::BasicEventPtr p1 = *basic_events().find("PumpOne");
- mef::BasicEventPtr p2 = *basic_events().find("PumpTwo");
- mef::BasicEventPtr v1 = *basic_events().find("ValveOne");
- mef::BasicEventPtr v2 = *basic_events().find("ValveTwo");
+ mef::BasicEvent* p1 = basic_events().find("PumpOne")->get();
+ mef::BasicEvent* p2 = basic_events().find("PumpTwo")->get();
+ mef::BasicEvent* v1 = basic_events().find("ValveOne")->get();
+ mef::BasicEvent* v2 = basic_events().find("ValveTwo")->get();
ASSERT_NO_THROW(p1->p());
ASSERT_NO_THROW(p2->p());
diff --git a/tests/serialization_tests.cc b/tests/serialization_tests.cc
new file mode 100644
index 0000000..b8b64a6
--- /dev/null
+++ b/tests/serialization_tests.cc
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 Olzhas Rakhimov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "serialization.h"
+
+#include <sstream>
+
+#include <gtest/gtest.h>
+#include <libxml++/libxml++.h>
+
+#include "env.h"
+#include "initializer.h"
+#include "settings.h"
+
+namespace scram {
+namespace mef {
+
+TEST(SerializationTest, InputOutput) {
+ static xmlpp::RelaxNGValidator validator(Env::install_dir() +
+ "/share/scram/gui.rng");
+
+ std::vector<std::vector<std::string>> inputs = {
+ {"./share/scram/input/fta/correct_tree_input.xml"},
+ {"./share/scram/input/fta/correct_tree_input_with_probs.xml"},
+ {"./share/scram/input/fta/missing_bool_constant.xml"},
+ {"./share/scram/input/fta/null_gate_with_label.xml"},
+ {"./share/scram/input/fta/flavored_types.xml"},
+ {"./share/scram/input/TwoTrain/two_train.xml"},
+ {"./share/scram/input/fta/correct_formulas.xml"},
+ {"./share/scram/input/Theatre/theatre.xml"},
+ {"./share/scram/input/Baobab/baobab2.xml",
+ "./share/scram/input/Baobab/baobab2-basic-events.xml"}};
+ for (const auto& input : inputs) {
+ std::shared_ptr<Model> model;
+ ASSERT_NO_THROW(model = mef::Initializer(input, core::Settings{}).model());
+ std::stringstream output;
+ ASSERT_NO_THROW(Serialize(*model, output)) << input.front();
+
+ xmlpp::DomParser parser;
+ ASSERT_NO_THROW(parser.parse_stream(output)) << input.front();
+ ASSERT_NO_THROW(validator.validate(parser.get_document())) << input.front();
+ }
+}
+
+} // namespace mef
+} // namespace scram
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/scram.git
More information about the debian-science-commits
mailing list