initial commit

This commit is contained in:
Björn Ellensohn 2023-08-24 12:23:28 +02:00
commit d0f16e6701
12 changed files with 768 additions and 0 deletions

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# my_ros2_docker_image
Following this you will be able to do multiplatform builds with docker buildx.
For the build with docker buildx you will need binfmt dependencies for arm64 architecture, as we are doing a multiplatform build (Works on PCs as well as Jetson Nano, Raspberry Pi, Apple Silicon Devices).
## What you will want to change:
### my.repos and my_extended.repos:
These are yaml files containing the repositories you may want to include into your final ros2 workspace.
For a multi-stage build for instance, I define my base image repositories in "my.repos" and the extended image repositories in "my_extended.repos"
## To build the image for example_service (and push it to your container registry):
docker buildx bake example_service --load
docker buildx bake example_service --push
## To run example_service:
docker compose up -d example_service

28
dependencies.amd64.repos Normal file
View File

@ -0,0 +1,28 @@
repositories:
# CPS RMP lite 220 Dependencies
segwayrmp:
type: git
url: https://github.com/bjoernellens1/segwayrmp
version: humble
segway_msgs:
type: git
url: https://github.com/bjoernellens1/segway_msgs
version: main
RPlidar_ROS2:
type: git
url: https://github.com/bjoernellens1/sllidar_ros2
version: main
rmp220_teleop:
type: git
url: https://github.com/bjoernellens1/rmp220_teleop.git
version: main
rmp220_middleware:
type: git
url: https://github.com/bjoernellens1/rmp220_middleware.git
version: main

28
dependencies.arm64.repos Normal file
View File

@ -0,0 +1,28 @@
repositories:
# CPS RMP lite 220 Dependencies
segwayrmp:
type: git
url: https://github.com/bjoernellens1/segwayrmp
version: humble-arm64
segway_msgs:
type: git
url: https://github.com/bjoernellens1/segway_msgs
version: main
RPlidar_ROS2:
type: git
url: https://github.com/bjoernellens1/sllidar_ros2
version: main
rmp220_teleop:
type: git
url: https://github.com/bjoernellens1/rmp220_teleop.git
version: main
rmp220_middleware:
type: git
url: https://github.com/bjoernellens1/rmp220_middleware.git
version: main

259
docker-compose.yaml Normal file
View File

@ -0,0 +1,259 @@
version: "3.9"
services: # in docker compose, service are definitions of your docker containers
# Base image containing dependencies for example the robot controller
base:
image: ghcr.io/bjoernellens1/ros2_rmp/rmp:base # this will be the output image tag
build:
context: .
dockerfile: docker/Dockerfile # the Dockerfilespeciefies the steps taken in the build process.
args:
ROS_DISTRO: humble
target: base # here you specify the build target in the Dockerfile
x-bake:
platforms:
#- linux/arm64 uncomment if arm64 platform is needed (like jetson nano or raspberry pi)
- linux/amd64
# Interactive shell enable
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host # host networking makes life easier
ipc: host # --> faster communications between containers
# Needed to display graphical applications
privileged: true # also needed for hardware access like joystick, cameras...
environment:
# Allows graphical programs in the container.
- DISPLAY=${DISPLAY}
- QT_X11_NO_MITSHM=1
- NVIDIA_DRIVER_CAPABILITIES=all
volumes:
# Allows graphical programs in the container.
- /tmp/.X11-unix:/tmp/.X11-unix:rw
- ${XAUTHORITY:-$HOME/.Xauthority}:/root/.Xauthority
devices: # all devices you may need inside the docker container (for instance I needed a joystick here)
- /dev/input/js0:/dev/input/js0 # mapping in docker is always host_path:container_path
# Overlay image containing the extended project packages. For instance Mapping, Navigation which may run on a separate PC, so I divided that from the base image.
overlay:
extends: base # this means you take the base image as reference and run your service on top of that
image: ghcr.io/bjoernellens1/ros2_rmp/rmp:overlay
build:
context: .
dockerfile: docker/Dockerfile
tags:
- ghcr.io/bjoernellens1/ros2_rmp/rmp:overlay
target: overlay
x-bake:
platforms:
#- linux/arm64 uncomment if arm64 platform is needed (like jetson nano or raspberry pi)
- linux/amd64
volumes:
- .:/repo
command: >
/bin/bash
# Additional dependencies for GUI applications: For my configuration, this last (biggest size) image will house the additional GUI tools like rviz2, gazebo, etc. This comes last as this extends the previous images. Won't be needed running on the robot. More suited for visualization, analysis or control on a separate PC.
guis:
extends: overlay
image: ghcr.io/bjoernellens1/ros2_rmp/rmp:guis
build:
context: .
dockerfile: docker/Dockerfile
tags:
- ghcr.io/bjoernellens1/ros2_rmp/rmp:guis
target: guis
x-bake:
platforms:
#- linux/arm64 uncomment if arm64 platform is needed (like jetson nano or raspberry pi)
- linux/amd64
#entrypoint: /bin/bash
command: >
/bin/bash
# The following service definitions do not include build instructions as they do not require additional content in the images. Meaning, if you already have the image locally, everything's fine. If you don't have the image locally, docker compose will download the image from the image registry for you. If docker compose does not have access to the image registry, it will build the corresponding image for you (so better make sur you have access).
# You can see these service definitions as an easy way to spin up separate containers for launching separate tasks. I use this to control the current operational mode of the robot (mapping or navigation an localization). In the end it's just spinning up launch files in separate containers.
# Robot State Publisher
rsp:
extends: overlay
command: >
ros2 launch cps_rmp220_support rsp.launch.py
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host
ipc: host
environment:
- ROS_DOMAIN_ID=5
# Controller
controller:
extends: base
command: >
ros2 run segwayrmp SmartCar --ros-args -r cmd_vel:=cmd_vel_out -p serial_full_name:=/dev/ttyUSB0
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
# Interactive shell
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host
ipc: host
# Needed to display graphical applications
privileged: true
# depends_on:
# - robot_state_publisher
environment:
- ROS_DOMAIN_ID=5
restart: unless-stopped
# teleop
teleop:
extends: base
command: >
ros2 launch rmp220_teleop robot_joystick.launch.py
devices:
- /dev/input/js0:/dev/input/js0
# Interactive shell
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host
ipc: host
# Needed to display graphical applications
privileged: true
environment:
- ROS_DOMAIN_ID=5
# lidar
lidar:
extends: overlay
command: >
ros2 launch cps_rmp220_support robot_lidar.launch.py serial_port:=/dev/ttyUSB0
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host
ipc: host
# depends_on:
# - robot_state_publisher
environment:
- ROS_DOMAIN_ID=5
devices:
- /dev/ttyUSB1:/dev/ttyUSB1
- /dev/ttyUSB0:/dev/ttyUSB0
# localiaztion by ekf node
ekf:
extends: overlay
command: >
ros2 launch cps_rmp220_support robot_localization.launch.py
# Interactive shell
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host
ipc: host
environment:
- ROS_DOMAIN_ID=5
# mapping
mapping:
extends: overlay
command: >
ros2 launch cps_rmp220_support robot_mapping.launch.py
# Interactive shell
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host
ipc: host
environment:
- ROS_DOMAIN_ID=5
# slam_localization
localization:
extends: overlay
command: >
ros2 launch cps_rmp220_support robot_mapping_localization.launch.py map_file_name:=/repo/maps/map.yaml
# Interactive shell
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host
ipc: host
environment:
- ROS_DOMAIN_ID=5
# amcl_localization
amcl:
extends: overlay
command: >
ros2 launch cps_rmp220_support robot_amcl.launch.py map:=/repo/maps/map.yaml
# Interactive shell
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host
ipc: host
environment:
- ROS_DOMAIN_ID=5
# navigation
navigation:
extends: overlay
command: >
ros2 launch cps_rmp220_support robot_navigation.launch.py
map_subscribe_transient_local:=true
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host
ipc: host
environment:
- ROS_DOMAIN_ID=5
# bash
bash:
extends: overlay
command: >
/bin/bash
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host
ipc: host
devices:
- /dev/input/js0:/dev/input/js0
environment:
- ROS_DOMAIN_ID=5
# rviz2
rviz2:
extends: guis
# command: >
# ros2 launch cps_rmp220_support robot_rviz2.launch.py
command: >
ros2 launch cps_rmp220_support rviz.launch.py
# Interactive shell
stdin_open: true
tty: true
# Networking and IPC for ROS 2
network_mode: host
ipc: host
# Needed to display graphical applications
privileged: true
environment:
# Allows graphical programs in the container.
- DISPLAY=${DISPLAY}
- QT_X11_NO_MITSHM=1
- NVIDIA_DRIVER_CAPABILITIES=all
- ROS_DOMAIN_ID=5
volumes:
# Allows graphical programs in the container.
- /tmp/.X11-unix:/tmp/.X11-unix:rw
- ${XAUTHORITY:-$HOME/.Xauthority}:/root/.Xauthority

113
docker/Dockerfile Normal file
View File

@ -0,0 +1,113 @@
ARG ROS_DISTRO=humble
ARG UNDERLAY_WS=/rmp_ws
ARG TARGETPLATFORM
# This file should work for both amd64 and arm64 builds. Because segway did not release some source code we must copy some binaries for the specific architectures.
# That is why we must use some conditional statements to copy over the correct files during build process.
# Variable TARGETPLATFORM should be auomatically created during build process by docker.
########################################
# Base Image for Bot Mini Control #
########################################
FROM ros:${ROS_DISTRO} as base
ENV ROS_DISTRO=${ROS_DISTRO}
#ENV TARGETPLATFORM=$TARGETPLATFORM
SHELL ["/bin/bash", "-c"]
# Create Colcon workspace with external dependencies
RUN mkdir -p /rmp_ws/src
WORKDIR /rmp_ws/src
COPY dependencies.arm64.repos .
COPY dependencies.amd64.repos .
# Choose correct sources for architecture:
# Copy platform-specific files
# Use if condition to copy different files for different platforms
#RUN echo "TARGETPLATFORM is $TARGETPLATFORM"
RUN echo "Hardware platform is: $(uname -m)"
RUN uname -a
RUN echo "update base 23.08.2023"
RUN if [ "$(uname -m)" = "x86_64" ]; then \
echo "Copying files for linux/amd64"; \
mv dependencies.amd64.repos dependencies.repos; \
else \
echo "Copying files for linux/arm64"; \
mv dependencies.arm64.repos dependencies.repos; \
fi
#RUN vcs import < dependencies.repos
RUN vcs import < dependencies.repos;
# Build the base Colcon workspace, installing dependencies first.
WORKDIR /rmp_ws
RUN source /opt/ros/${ROS_DISTRO}/setup.bash \
&& apt-get update -y \
&& rosdep install --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} -y
RUN source /opt/ros/${ROS_DISTRO}/setup.bash \
&& colcon build --symlink-install
# Use Cyclone DDS as middleware and install xacro
RUN apt-get update && apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-rmw-cyclonedds-cpp \
ros-${ROS_DISTRO}-xacro \
&& rm -rf /var/lib/apt/lists/*
ENV RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
ENV UNDERLAY_WS=${UNDERLAY_WS}
# Set up the entrypoint
COPY ./docker/entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
###########################################
# Overlay Image for RMP Control #
###########################################
FROM base AS overlay
# Create an overlay Colcon workspace
RUN mkdir -p /overlay_ws/src
WORKDIR /overlay_ws/src
COPY overlay.repos .
RUN vcs import < overlay.repos
WORKDIR /overlay_ws
RUN echo "update overlay 22.08.2023"
RUN source /rmp_ws/install/setup.bash \
&& apt-get update \
&& rosdep install --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} -y \
&& rm -rf /var/lib/apt/lists/*
RUN source /rmp_ws/install/setup.bash \
&& colcon build --symlink-install
# Set up the entrypoint
COPY ./docker/entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
###########################################
# GUI Additions for Ros2 #
###########################################
FROM overlay AS guis
# Install additional GUI tools
RUN source /rmp_ws/install/setup.bash \
&& apt-get update \
&& apt-get install -y --no-install-recommends ros-${ROS_DISTRO}-rviz2 \
&& rm -rf /var/lib/apt/lists/*
# Set up the entrypoint
ENTRYPOINT [ "/entrypoint.sh" ]
LABEL org.opencontainers.image.source=https://github.com/bjoernellens1/ros2_rmp

61
docker/Dockerfile.amd64 Normal file
View File

@ -0,0 +1,61 @@
ARG ROS_DISTRO=humble
ARG UNDERLAY_WS=/rmp_ws
########################################
# Base Image for Bot Mini Control #
########################################
FROM osrf/ros:${ROS_DISTRO}-desktop as base
ENV ROS_DISTRO=${ROS_DISTRO}
SHELL ["/bin/bash", "-c"]
# Create Colcon workspace with external dependencies
RUN mkdir -p /rmp_ws/src
WORKDIR /rmp_ws/src
COPY dependencies.repos .
RUN vcs import < dependencies.amd64.repos
# Build the base Colcon workspace, installing dependencies first.
WORKDIR /rmp_ws
RUN source /opt/ros/${ROS_DISTRO}/setup.bash \
&& apt-get update -y \
&& rosdep install --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} -y \
&& colcon build --symlink-install
# Use Cyclone DDS as middleware
RUN apt-get update && apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-rmw-cyclonedds-cpp
ENV RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
ENV UNDERLAY_WS=${UNDERLAY_WS}
###########################################
# Overlay Image for Bot Mini Control #
###########################################
FROM base AS overlay
# Create an overlay Colcon workspace
RUN mkdir -p /overlay_ws/src
WORKDIR /overlay_ws/src
RUN echo "Dummy step"
COPY overlay.repos .
RUN vcs import < overlay.repos
WORKDIR /overlay_ws
# COPY ./tb3_autonomy/ ./src/tb3_autonomy/
# COPY ./tb3_worlds/ ./src/tb3_worlds/
RUN source /rmp_ws/install/setup.bash \
&& colcon build --symlink-install \
&& rosdep install --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} -y
# Set up the entrypoint
COPY ./docker/entrypoint.sh /
RUN chmod +x /entrypoint.sh
COPY ./controller_startup.sh /
RUN chmod +x /controller_startup.sh
COPY ./localization_startup.sh /
RUN chmod +x /localization_startup.sh
ENTRYPOINT [ "/entrypoint.sh" ]

62
docker/Dockerfile.arm64 Normal file
View File

@ -0,0 +1,62 @@
ARG ROS_DISTRO=humble
ARG UNDERLAY_WS=/rmp_ws
########################################
# Base Image for Bot Mini Control #
########################################
#FROM --platform=linux/arm64 ros:${ROS_DISTRO} as base
FROM ros:${ROS_DISTRO} as base
ENV ROS_DISTRO=${ROS_DISTRO}
SHELL ["/bin/bash", "-c"]
# Create Colcon workspace with external dependencies
RUN mkdir -p /rmp_ws/src
WORKDIR /rmp_ws/src
COPY dependencies.repos .
RUN vcs import < dependencies.arm64.repos
# Build the base Colcon workspace, installing dependencies first.
WORKDIR /rmp_ws
RUN source /opt/ros/${ROS_DISTRO}/setup.bash \
&& apt-get update -y \
&& rosdep install --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} -y \
&& colcon build --symlink-install
# Use Cyclone DDS as middleware
RUN apt-get update && apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-rmw-cyclonedds-cpp
ENV RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
ENV UNDERLAY_WS=${UNDERLAY_WS}
###########################################
# Overlay Image for Bot Mini Control #
###########################################
FROM base AS overlay
# Create an overlay Colcon workspace
RUN mkdir -p /overlay_ws/src
WORKDIR /overlay_ws/src
RUN echo "Dummy step"
COPY overlay.repos .
RUN vcs import < overlay.repos
WORKDIR /overlay_ws
# COPY ./tb3_autonomy/ ./src/tb3_autonomy/
# COPY ./tb3_worlds/ ./src/tb3_worlds/
RUN source /rmp_ws/install/setup.bash \
&& colcon build --symlink-install \
&& rosdep install --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} -y
# Set up the entrypoint
COPY ./docker/entrypoint.sh /
RUN chmod +x /entrypoint.sh
COPY ./controller_startup.sh /
RUN chmod +x /controller_startup.sh
COPY ./localization_startup.sh /
RUN chmod +x /localization_startup.sh
ENTRYPOINT [ "/entrypoint.sh" ]

27
docker/entrypoint.sh Normal file
View File

@ -0,0 +1,27 @@
#!/bin/bash
# Basic entrypoint for ROS / Colcon Docker containers
UNDERLAY_WS=/rmp_ws
# Source ROS 2
source /opt/ros/${ROS_DISTRO}/setup.bash
echo "Sourced ROS 2 ${ROS_DISTRO}"
# Source the base workspace, if built
if [ -f ${UNDERLAY_WS}/install/setup.bash ]
then
source ${UNDERLAY_WS}/install/setup.bash
#export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:$(ros2 pkg prefix turtlebot3_gazebo)/share/turtlebot3_gazebo/models
echo "Sourced CPS RMP 220 base workspace"
fi
# Source the overlay workspace, if built
if [ -f /overlay_ws/install/setup.bash ]
then
source /overlay_ws/install/setup.bash
#export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:$(ros2 pkg prefix tb3_worlds)/share/tb3_worlds/models
echo "Sourced CPS RMP 220 Overlay workspace"
fi
# Execute the command passed into this entrypoint
exec "$@"

27
init_repo.sh Normal file
View File

@ -0,0 +1,27 @@
#!/bin/bash
# Basic entrypoint for ROS / Colcon Docker containers
# Source ROS 2
source /opt/ros/${ROS_DISTRO}/setup.bash
echo "Sourced ROS 2 ${ROS_DISTRO}"
# Source the base workspace, if built
if [ -f ${UNDERLAY_WS}/install/setup.bash ]
then
source ${UNDERLAY_WS}/install/setup.bash
#export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:$(ros2 pkg prefix turtlebot3_gazebo)/share/turtlebot3_gazebo/models
echo "Sourced Bot Mini base workspace"
fi
# Source the overlay workspace, if built
if [ -f /overlay_ws/install/setup.bash ]
then
source /overlay_ws/install/setup.bash
#export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:$(ros2 pkg prefix tb3_worlds)/share/tb3_worlds/models
echo "Sourced Bot Mini overlay workspace"
fi
# Copy over files
cp -r /overlay_ws/src/* /repo/src/
wait

3
install_portainer.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
docker volume create portainer_data
docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest

11
overlay.repos Normal file
View File

@ -0,0 +1,11 @@
repositories:
cps_rmp220_support:
type: git
url: https://github.com/bjoernellens1/cps_rmp220_support.git
version: main
cam_openCV:
type: git
url: https://github.com/bjoernellens1/ros2_cam_openCV.git
version: main

133
test.rviz Normal file
View File

@ -0,0 +1,133 @@
Panels:
- Class: rviz_common/Displays
Help Height: 85
Name: Displays
Property Tree Widget:
Expanded:
- /Global Options1
- /Status1
Splitter Ratio: 0.5
Tree Height: 528
- Class: rviz_common/Selection
Name: Selection
- Class: rviz_common/Tool Properties
Expanded:
- /2D Goal Pose1
- /Publish Point1
Name: Tool Properties
Splitter Ratio: 0.5886790156364441
- Class: rviz_common/Views
Expanded:
- /Current View1
Name: Views
Splitter Ratio: 0.5
- Class: rviz_common/Time
Experimental: false
Name: Time
SyncMode: 0
SyncSource: ""
Visualization Manager:
Class: ""
Displays:
- Alpha: 0.5
Cell Size: 1
Class: rviz_default_plugins/Grid
Color: 160; 160; 164
Enabled: true
Line Style:
Line Width: 0.029999999329447746
Value: Lines
Name: Grid
Normal Cell Count: 0
Offset:
X: 0
Y: 0
Z: 0
Plane: XY
Plane Cell Count: 10
Reference Frame: <Fixed Frame>
Value: true
Enabled: true
Global Options:
Background Color: 48; 48; 48
Fixed Frame: map
Frame Rate: 30
Name: root
Tools:
- Class: rviz_default_plugins/Interact
Hide Inactive Objects: true
- Class: rviz_default_plugins/MoveCamera
- Class: rviz_default_plugins/Select
- Class: rviz_default_plugins/FocusCamera
- Class: rviz_default_plugins/Measure
Line color: 128; 128; 0
- Class: rviz_default_plugins/SetInitialPose
Covariance x: 0.25
Covariance y: 0.25
Covariance yaw: 0.06853891909122467
Topic:
Depth: 5
Durability Policy: Volatile
History Policy: Keep Last
Reliability Policy: Reliable
Value: /initialpose
- Class: rviz_default_plugins/SetGoal
Topic:
Depth: 5
Durability Policy: Volatile
History Policy: Keep Last
Reliability Policy: Reliable
Value: /goal_pose
- Class: rviz_default_plugins/PublishPoint
Single click: true
Topic:
Depth: 5
Durability Policy: Volatile
History Policy: Keep Last
Reliability Policy: Reliable
Value: /clicked_point
Transformation:
Current:
Class: rviz_default_plugins/TF
Value: true
Views:
Current:
Class: rviz_default_plugins/Orbit
Distance: 10
Enable Stereo Rendering:
Stereo Eye Separation: 0.05999999865889549
Stereo Focal Distance: 1
Swap Stereo Eyes: false
Value: false
Focal Point:
X: 0
Y: 0
Z: 0
Focal Shape Fixed Size: true
Focal Shape Size: 0.05000000074505806
Invert Z Axis: false
Name: Current View
Near Clip Distance: 0.009999999776482582
Pitch: 0.785398006439209
Target Frame: <Fixed Frame>
Value: Orbit (rviz)
Yaw: 0.785398006439209
Saved: ~
Window Geometry:
Displays:
collapsed: false
Height: 845
Hide Left Dock: false
Hide Right Dock: false
QMainWindow State: 000000ff00000000fd00000004000000000000015a000002a6fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000006b00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c0061007900730100000044000002a6000000eb00fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000002a6fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a005600690065007700730100000044000002a6000000b900fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004af0000003efc0100000002fb0000000800540069006d00650100000000000004af000002d200fffffffb0000000800540069006d0065010000000000000450000000000000000000000238000002a600000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000
Selection:
collapsed: false
Time:
collapsed: false
Tool Properties:
collapsed: false
Views:
collapsed: false
Width: 1199
X: 199
Y: 115