Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • gsaurel/gepetto-viewer-corba
  • gepetto/gepetto-viewer-corba
2 results
Show changes
Commits on Source (183)
# pre-commit run -a (Guilhem Saurel, 2022-10-20)
b04b074db9e058d06b5db27316afbb3d9eea9173
# format (Guilhem Saurel, 2022-04-05)
62b5dafa1a15795521ab3c31d321b37fd46ae69f
name: "CI - Nix"
on:
push:
jobs:
nix:
runs-on: "${{ matrix.os }}-latest"
strategy:
matrix:
os: [ubuntu, macos]
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v27
- uses: cachix/cachix-action@v15
with:
name: gepetto
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix build -L
include: http://rainboard.laas.fr/project/gepetto-viewer-corba/.gitlab-ci.yml
include: https://rainboard.laas.fr/project/gepetto-viewer-corba/.gitlab-ci.yml
[submodule "cmake"]
path = cmake
url = git://github.com/jrl-umi3218/jrl-cmakemodules.git
url = https://github.com/jrl-umi3218/jrl-cmakemodules.git
pull_request_rules:
- name: merge automatically when CI passes and PR is approved
conditions:
- check-success = "gitlab-ci"
- check-success = "nix (macos)"
- check-success = "nix (ubuntu)"
- check-success = "pre-commit.ci - pr"
- or:
- author = pre-commit-ci[bot]
- author = dependabot[bot]
actions:
merge:
ci:
autoupdate_branch: devel
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.3
hooks:
- id: ruff
args:
- --fix
- --exit-non-zero-on-fix
- id: ruff-format
- repo: https://github.com/cheshirekow/cmake-format-precommit
rev: v0.6.13
hooks:
- id: cmake-format
- repo: https://github.com/pappasam/toml-sort
rev: v0.24.2
hooks:
- id: toml-sort-fix
exclude: poetry.lock
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v19.1.5
hooks:
- id: clang-format
args:
- --style=Google
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-added-large-files
- id: check-ast
- id: check-executables-have-shebangs
- id: check-json
- id: check-merge-conflict
- id: check-symlinks
- id: check-toml
- id: check-yaml
- id: debug-statements
- id: destroyed-symlinks
- id: detect-private-key
- id: end-of-file-fixer
- id: fix-byte-order-marker
- id: mixed-line-ending
- id: trailing-whitespace
Developped in LAAS-CNRS France.
Mathieu Geisert
# Copyright (c) 2014 CNRS
# Author: Mathieu Geisert, Florent Lamiraux
# Copyright (c) 2014, 2020 CNRS Author: Mathieu Geisert, Florent Lamiraux,
# Guilhem Saurel
#
# This file is part of gepetto-viewer-corba.
# gepetto-viewer-corba is free software: you can redistribute it
# and/or modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either version
# 3 of the License, or (at your option) any later version.
# This file is part of gepetto-viewer-corba. gepetto-viewer-corba is free
# software: you can redistribute it and/or modify it under the terms of the GNU
# Lesser General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
#
# gepetto-viewer-corba 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 Lesser Public License for more details. You should have
# received a copy of the GNU Lesser General Public License along with
# gepetto-viewer-corba. If not, see
# gepetto-viewer-corba 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 Lesser Public License
# for more details. You should have received a copy of the GNU Lesser General
# Public License along with gepetto-viewer-corba. If not, see
# <http://www.gnu.org/licenses/>.
# Requires at least CMake 2.8 to configure the package.
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
SET(PROJECT_NAME gepetto-viewer-corba)
SET(PROJECT_DESCRIPTION "Corba server for gepetto-viewer")
SET(PROJECT_URL "https://github.com/Gepetto/${PROJECT_NAME}")
SET(CXX_DISABLE_WERROR true)
INCLUDE(cmake/base.cmake)
INCLUDE(cmake/idl.cmake)
INCLUDE(cmake/python.cmake)
INCLUDE(cmake/boost.cmake)
INCLUDE(cmake/test.cmake)
INCLUDE(cmake/apple.cmake)
SET(CLIENT_ONLY FALSE CACHE BOOL "Set to true to install the client only")
COMPUTE_PROJECT_ARGS(PROJECT_ARGS LANGUAGES CXX)
PROJECT(${PROJECT_NAME} ${PROJECT_ARGS})
IF(APPLE)
APPLY_DEFAULT_APPLE_CONFIGURATION()
ENDIF(APPLE)
# {{{ C++ and Python client.
# Dependencies
FINDPYTHON()
ADD_REQUIRED_DEPENDENCY("omniORB4 >= 4.1.4")
SET(${PROJECT_NAME}_HEADERS
include/gepetto/viewer/corba/client.hh
)
cmake_minimum_required(VERSION 3.10)
set(PROJECT_NAME gepetto-viewer-corba)
set(PROJECT_DESCRIPTION "Corba server for gepetto-viewer")
set(PROJECT_URL "https://github.com/Gepetto/${PROJECT_NAME}")
set(PROJECT_USE_CMAKE_EXPORT TRUE)
set(PROJECT_USE_KEYWORD_LINK_LIBRARIES TRUE)
set(CXX_DISABLE_WERROR true)
set(DOXYGEN_USE_TEMPLATE_CSS TRUE)
# Check if the submodule cmake have been initialized
set(JRL_CMAKE_MODULES "${CMAKE_CURRENT_LIST_DIR}/cmake")
if(EXISTS "${JRL_CMAKE_MODULES}/base.cmake")
message(STATUS "JRL cmakemodules found in 'cmake/' git submodule")
else()
find_package(jrl-cmakemodules QUIET CONFIG)
if(jrl-cmakemodules_FOUND)
get_property(
JRL_CMAKE_MODULES
TARGET jrl-cmakemodules::jrl-cmakemodules
PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
message(STATUS "JRL cmakemodules found on system at ${JRL_CMAKE_MODULES}")
elseif(${CMAKE_VERSION} VERSION_LESS "3.14.0")
message(
FATAL_ERROR
"\nCan't find jrl-cmakemodules. Please either:\n"
" - use git submodule: 'git submodule update --init'\n"
" - or install https://github.com/jrl-umi3218/jrl-cmakemodules\n"
" - or upgrade your CMake version to >= 3.14 to allow automatic fetching\n"
)
else()
message(STATUS "JRL cmakemodules not found. Let's fetch it.")
include(FetchContent)
FetchContent_Declare(
"jrl-cmakemodules"
GIT_REPOSITORY "https://github.com/jrl-umi3218/jrl-cmakemodules.git")
FetchContent_MakeAvailable("jrl-cmakemodules")
FetchContent_GetProperties("jrl-cmakemodules" SOURCE_DIR JRL_CMAKE_MODULES)
endif()
endif()
include("${JRL_CMAKE_MODULES}/base.cmake")
include("${JRL_CMAKE_MODULES}/idl.cmake")
include("${JRL_CMAKE_MODULES}/apple.cmake")
set(CLIENT_ONLY
FALSE
CACHE BOOL "Set to true to install the client only")
compute_project_args(PROJECT_ARGS LANGUAGES CXX)
project(${PROJECT_NAME} ${PROJECT_ARGS})
if(APPLE)
apply_default_apple_configuration()
endif(APPLE)
# {{{ C++ and Python client. Dependencies
add_required_dependency("omniORB4 >= 4.1.4")
set(${PROJECT_NAME}_HEADERS
include/gepetto/viewer/corba/api.hh include/gepetto/viewer/corba/client.hh
include/gepetto/viewer/corba/conversions.hh)
# }}}
# {{{ C++ server.
IF(NOT CLIENT_ONLY)
if(NOT CLIENT_ONLY)
# {{{ Dependencies for the server.
# Tells pkg-config to read qtversion and cmake_plugin from pkg config file.
LIST(APPEND PKG_CONFIG_ADDITIONAL_VARIABLES qtversion cmake_plugin)
ADD_REQUIRED_DEPENDENCY("gepetto-viewer >= 4.3.0")
add_project_dependency("gepetto-viewer" REQUIRED)
# Get desired Qt version
string(REPLACE "." ";" DESIRED_QT_VERSION_LIST ${GEPETTO_VIEWER_QTVERSION})
list(GET DESIRED_QT_VERSION_LIST 0 DESIRED_QT_VERSION_MAJOR)
IF(${DESIRED_QT_VERSION_MAJOR} EQUAL 4)
SET(PROJECT_USE_QT4 True)
ELSEIF(${DESIRED_QT_VERSION_MAJOR} EQUAL 5)
SET(PROJECT_USE_QT4 False)
ELSE()
MESSAGE(FATAL_ERROR "This package is only compatible with Qt 4 and Qt 5")
ENDIF()
MESSAGE(STATUS "Looking for Qt ${DESIRED_QT_VERSION_MAJOR}.")
SET(CMAKE_INCLUDE_CURRENT_DIR ON)
IF(PROJECT_USE_QT4)
FIND_PACKAGE(Qt4 REQUIRED QtCore QtGui QtNetwork)
SET(PKG_CONFIG_EXTRA "qtversion=${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}")
INCLUDE(${QT_USE_FILE})
ELSE(PROJECT_USE_QT4)
FOREACH (component "Core" "Widgets" "Gui" "Network" "Concurrent")
FIND_PACKAGE ("Qt5${component}" REQUIRED)
LIST(APPEND QT_INCLUDE_DIRS ${Qt5${component}_INCLUDE_DIRS})
LIST(APPEND QT_LIBRARIES ${Qt5${component}_LIBRARIES} )
ENDFOREACH (component "Core" "Widgets" "Gui" "OpenGL" "Network")
SET(PKG_CONFIG_EXTRA "qtversion=${Qt5Core_VERSION}")
ENDIF(PROJECT_USE_QT4)
INCLUDE_DIRECTORIES(SYSTEM ${EIGEN3_INCLUDE_DIRS} ${QT_INCLUDE_DIRS})
if(${DESIRED_QT_VERSION_MAJOR} EQUAL 4)
set(PROJECT_USE_QT4 True)
elseif(${DESIRED_QT_VERSION_MAJOR} EQUAL 5)
set(PROJECT_USE_QT4 False)
else()
message(FATAL_ERROR "This package is only compatible with Qt 4 and Qt 5")
endif()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(GEPETTO_VIEWER_CORBA_QTVERSION "${GEPETTO_VIEWER_QTVERSION}")
set(PKG_CONFIG_EXTRA "qtversion=${GEPETTO_VIEWER_CORBA_QTVERSION}")
set(PACKAGE_EXTRA_MACROS
"set(GEPETTO_VIEWER_CORBA_QTVERSION ${GEPETTO_VIEWER_CORBA_QTVERSION})")
# }}}
# {{{ Packaging
PKG_CONFIG_APPEND_LIBS(${PROJECT_NAME})
pkg_config_append_libs(${PROJECT_NAME})
# For backward compatibility
SET(PKG_CONFIG_EXTRA "${PKG_CONFIG_EXTRA}\ncmake_plugin=${GEPETTO_VIEWER_CMAKE_PLUGIN}")
INCLUDE(${GEPETTO_VIEWER_CMAKE_PLUGIN})
set(PKG_CONFIG_EXTRA
"${PKG_CONFIG_EXTRA}\ncmake_plugin=${GEPETTO_VIEWER_CMAKE_PLUGIN}")
# }}}
# {{{ Set list of headers
SET (${PROJECT_NAME}_HEADERS
${CMAKE_SOURCE_DIR}/include/gepetto/viewer/corba/server.hh
${CMAKE_SOURCE_DIR}/include/gepetto/viewer/corba/fwd.hh
${CMAKE_SOURCE_DIR}/include/gepetto/gui/omniorb/url.hh
)
list(APPEND ${PROJECT_NAME}_HEADERS
${CMAKE_SOURCE_DIR}/include/gepetto/gui/omniorb/url.hh)
# }}}
ENDIF(NOT CLIENT_ONLY)
endif(NOT CLIENT_ONLY)
# }}}
ADD_SUBDIRECTORY(src)
ADD_SUBDIRECTORY(doc)
add_subdirectory(src)
add_subdirectory(doc)
IF(NOT CLIENT_ONLY)
ADD_SUBDIRECTORY(plugins)
ADD_SUBDIRECTORY(blender)
ADD_SUBDIRECTORY(examples EXCLUDE_FROM_ALL)
ADD_SUBDIRECTORY(tests)
ENDIF(NOT CLIENT_ONLY)
if(NOT CLIENT_ONLY)
add_subdirectory(plugins)
add_subdirectory(blender)
add_subdirectory(examples EXCLUDE_FROM_ALL)
add_subdirectory(tests)
endif(NOT CLIENT_ONLY)
install(FILES package.xml DESTINATION share/${PROJECT_NAME})
# vim: foldmethod=marker foldlevel=0
Copyright (c) 2013-2014,
Copyright (c) 2013-2014,
LAAS-CNRS France
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright notice,
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the <ORGANIZATION> nor the names of its contributors
* Neither the name of the <ORGANIZATION> nor the names of its contributors
may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
......
-*- outline -*-
New in 6.0.0
* parameter meshDataRootDir has been removed from idl methods addUrdf, addUrdfCollision, addUrdfObjects.
New in 5.5.0
* Parameter meshDataRootDir has been removed from idl methods addUrdf, addUrdfCollision, addUrdfObjects.
* Add package.xml
New in 5.3.0
* Add python trivial test
......
# CORBA server/client for the Graphical Interface of Pinocchio and HPP
[![Building Status](https://travis-ci.org/gepetto/gepetto-viewer-corba.svg?branch=master)](https://travis-ci.org/gepetto/gepetto-viewer-corba)
[![Pipeline status](https://gepgitlab.laas.fr/gepetto/gepetto-viewer-corba/badges/master/pipeline.svg)](https://gepgitlab.laas.fr/gepetto/gepetto-viewer-corba/commits/master)
[![Coverage report](https://gepgitlab.laas.fr/gepetto/gepetto-viewer-corba/badges/master/coverage.svg?job=doc-coverage)](http://projects.laas.fr/gepetto/doc/gepetto/gepetto-viewer-corba/master/coverage/)
[![Pipeline status](https://gitlab.laas.fr/gepetto/gepetto-viewer-corba/badges/master/pipeline.svg)](https://gitlab.laas.fr/gepetto/gepetto-viewer-corba/commits/master)
[![Coverage report](https://gitlab.laas.fr/gepetto/gepetto-viewer-corba/badges/master/coverage.svg?job=doc-coverage)](https://gepettoweb.laas.fr/doc/gepetto/gepetto-viewer-corba/master/coverage/)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/gepetto/gepetto-viewer-corba/master.svg)](https://results.pre-commit.ci/latest/github/gepetto/gepetto-viewer-corba)
## Summary
* [gepetto-viewer-corba](#gepetto-viewer-corba)
* [Setup](#setup)
* [Dependencies](#dependencies)
* [Install standalone urdfdom](#install-standalone-urdfdom)
* [gepetto-gui](#gepetto-gui)
* [Usage](#usage)
* [Basic usage](#basic-usage)
......@@ -60,26 +62,12 @@ have to be available on your machine.
- Libraries:
- omniORB4 (version >= 4.1.4)
- openscenegraph (version >= 3.2)
- urdfdom (version >= 0.3.0)
- GepettoViewer (Graphical Interface of Pinocchio and HPP)
- Binaries:
- omniNames (installed by sudo apt-get install omniorb-nameserver)
- System tools:
- CMake (>=2.6)
- pkg-config
- usual compilation tools (GCC/G++, make, etc.)
### Install standalone urdfdom
In order to read urdf files (see http://wiki.ros.org/urdf for the description), one haves to install the urdfdom package which can come either along ROS library or be installed as a standalone library. Next section describes the second procedure.
urdfdom depends on both console_bridge and urdfdom_headers. The installation of both dependencies can be done with the following command lines in a terminal :
- `git clone git://github.com/ros/console_bridge.git && cd console_bridge && mkdir build && cd build && cmake .. && make && sudo make install`
- `git clone git://github.com/ros/urdfdom_headers && cd urdfdom_headers && mkdir build && cd build && cmake .. && make && sudo make install`
Finally, you just need to apply the following command line to install urdfdom library :
- `git clone git://github.com/ros/urdfdom && cd urdfdom && mkdir build && cd build && cmake .. && make && sudo make install`
## gepetto-gui
## Usage
......
# Copyright (c) 2015 CNRS
# Author: Joseph Mirabel
# Copyright (c) 2015 CNRS Author: Joseph Mirabel
#
# This file is part of gepetto-viewer-corba.
# gepetto-viewer-corba is free software: you can redistribute it
# and/or modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either version
# 3 of the License, or (at your option) any later version.
# This file is part of gepetto-viewer-corba. gepetto-viewer-corba is free
# software: you can redistribute it and/or modify it under the terms of the GNU
# Lesser General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
#
# gepetto-viewer-corba 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 Lesser Public License for more details. You should have
# received a copy of the GNU Lesser General Public License along with
# gepetto-viewer-corba. If not, see
# gepetto-viewer-corba 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 Lesser Public License
# for more details. You should have received a copy of the GNU Lesser General
# Public License along with gepetto-viewer-corba. If not, see
# <http://www.gnu.org/licenses/>.
SET (BLENDER_EXE "" CACHE FILEPATH "Path to the blender executable")
set(BLENDER_EXE
""
CACHE FILEPATH "Path to the blender executable")
IF (EXISTS ${BLENDER_EXE})
CONFIGURE_FILE (install_addon.py.in ${CMAKE_CURRENT_BINARY_DIR}/install_addon.py)
# ADD_CUSTOM_COMMAND (OUTPUT nothing
# COMMAND echo "${BLENDER_EXE}"
# COMMENT "Installing the blender addon")
# COMMAND ${BLENDER_EXE}
# --background --enable-autoexec
# --python ${CMAKE_CURRENT_SOURCE_DIR}/install_addon.py
ADD_CUSTOM_TARGET (blender
PYTHONPATH=/usr/lib/python3/dist-packages/
${BLENDER_EXE} --background --enable-autoexec
--python ${CMAKE_CURRENT_BINARY_DIR}/install_addon.py
COMMENT "Installing the blender addon")
ENDIF (EXISTS ${BLENDER_EXE})
if(EXISTS ${BLENDER_EXE})
configure_file(install_addon.py.in
${CMAKE_CURRENT_BINARY_DIR}/install_addon.py)
# ADD_CUSTOM_COMMAND (OUTPUT nothing COMMAND echo "${BLENDER_EXE}" COMMENT
# "Installing the blender addon") COMMAND ${BLENDER_EXE} --background
# --enable-autoexec --python ${CMAKE_CURRENT_SOURCE_DIR}/install_addon.py
add_custom_target(
blender
PYTHONPATH=/usr/lib/python3/dist-packages/ ${BLENDER_EXE} --background
--enable-autoexec --python ${CMAKE_CURRENT_BINARY_DIR}/install_addon.py
COMMENT "Installing the blender addon")
endif(EXISTS ${BLENDER_EXE})
INSTALL (PROGRAMS urdf_to_blender.py DESTINATION bin)
install(PROGRAMS urdf_to_blender.py DESTINATION bin)
......@@ -22,102 +22,123 @@
# https://github.com/jmirabel/gepetto-viewer-corba/tree/devel/blender
###
import os
import bpy
import bpy_extras.io_utils
import yaml
bl_info = {
"author": "Joseph Mirabel",
"name" : "Gepetto Viewer Blender Addon",
"name": "Gepetto Viewer Blender Addon",
"category": "Import-Export",
"blender": (2,75,0),
"description": "Add functionality to import files generated using the Gepetto Viewer software",
"blender": (2, 75, 0),
"description": "Add functionality to import files generated "
"using the Gepetto Viewer software",
"location": "SpaceBar Search > YAML Gepetto or URDF blender",
"wiki_url": "https://github.com/jmirabel/gepetto-viewer-corba/tree/master/blender#readme",
"warning" : "Not heavily tested, feel free to report bug on github.",
"version" : (0,0),
"support" : "COMMUNITY"
}
"wiki_url": "https://github.com/jmirabel/gepetto-viewer-corba/tree/master/blender",
"warning": "Not heavily tested, feel free to report bug on github.",
"version": (0, 0),
"support": "COMMUNITY",
}
import bpy
import bpy_extras.io_utils
import re
import yaml, os
def loadmotion (filename):
with open (filename) as file:
data = yaml.load (file)
for frameId in range (len(data.keys())):
frameKey = "frame_" + str (frameId)
def loadmotion(filename):
with open(filename) as file:
data = yaml.load(file)
for frameId in range(len(data.keys())):
frameKey = "frame_" + str(frameId)
objPositions = data[frameKey]
for objName, pos in objPositions.items ():
for objName, pos in objPositions.items():
currentObj = bpy.context.scene.objects.get(objName)
if currentObj:
currentObj.rotation_mode = 'QUATERNION'
currentObj.rotation_mode = "QUATERNION"
posF = [float(x) for x in pos]
currentObj.location = posF[0:3]
currentObj.rotation_quaternion = posF[3:7]
currentObj.keyframe_insert (data_path="location", frame=frameId)
currentObj.keyframe_insert (data_path="rotation_quaternion", frame=frameId)
currentObj.keyframe_insert(data_path="location", frame=frameId)
currentObj.keyframe_insert(
data_path="rotation_quaternion", frame=frameId
)
else:
print("Unknown object " + objName)
def checkframe (filename, frameId):
with open (filename) as file:
data = yaml.load (file)
frameKey = "frame_" + str (frameId)
def checkframe(filename, frameId):
with open(filename) as file:
data = yaml.load(file)
frameKey = "frame_" + str(frameId)
objPositions = data[frameKey]
for objName, pos in objPositions.items ():
for objName, pos in objPositions.items():
currentObj = bpy.context.scene.objects.get(objName)
if currentObj:
currentObj.rotation_mode = 'QUATERNION'
currentObj.rotation_mode = "QUATERNION"
posF = [float(x) for x in pos]
currentObj.location = posF[0:3]
currentObj.rotation_quaternion = posF[3:7]
else:
print("Unknown object " + objName)
class YamlPathImport (bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
class YamlPathImport(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
bl_idname = "import.gepettoimport"
bl_label = "Import a YAML Gepetto Viewer path file"
files = bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement,)
directory = bpy.props.StringProperty(subtype='DIR_PATH',)
files = bpy.props.CollectionProperty(
name="File Path",
type=bpy.types.OperatorFileListElement,
)
directory = bpy.props.StringProperty(
subtype="DIR_PATH",
)
def execute(self, context):
dir = self.directory
for f in self.files:
fullname = os.path.join (dir, f.name)
self.report ({'INFO'}, "Loading " + str(fullname))
fullname = os.path.join(dir, f.name)
self.report({"INFO"}, "Loading " + str(fullname))
loadmotion(fullname)
return {'FINISHED'}
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
return {"RUNNING_MODAL"}
class UrdfToBlendImport (bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
class UrdfToBlendImport(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
bl_idname = "import.urdf_to_blendimport"
bl_label = "Import a URDF blender script"
files = bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement,)
directory = bpy.props.StringProperty(subtype='DIR_PATH',)
files = bpy.props.CollectionProperty(
name="File Path",
type=bpy.types.OperatorFileListElement,
)
directory = bpy.props.StringProperty(
subtype="DIR_PATH",
)
def execute(self, context):
dir = self.directory
for f in self.files:
fullname = os.path.join (dir, f.name)
self.report ({'INFO'}, "Loading " + str(fullname))
fullname = os.path.join(dir, f.name)
self.report({"INFO"}, "Loading " + str(fullname))
exec(open(fullname).read())
return {'FINISHED'}
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
return {"RUNNING_MODAL"}
def register():
bpy.utils.register_class(YamlPathImport)
bpy.utils.register_class(UrdfToBlendImport)
def unregister():
bpy.utils.unregister_class(YamlPathImport)
bpy.utils.unregister_class(UrdfToBlendImport)
if __name__ == "__main__":
register()
#!/usr/bin/env python2.7
from __future__ import print_function
import roslib; roslib.load_manifest('urdf_parser_py')
import rospy
import resource_retriever
import sys, getopt
import os.path
import getopt
import inspect
import os.path
import sys
import resource_retriever
import roslib
import urdf_parser_py.urdf as urdf
def usage ():
print (os.path.basename(sys.argv[0]) + " [--env] [-p <prefix>] -i <urdf-file> -o <blender-script>\n")
print ("Arguments:")
print ("\t-i urdf-file \t[mandatory]\tinput URDF file")
print ("\t-o blender-script\t[mandatory]\toutput blender script")
print ("\t-p prefix \t[optional] \tprefix of object names")
print ("\t--env \t[optional] \tobject static transform will be ignored")
roslib.load_manifest("urdf_parser_py")
def usage():
print(
os.path.basename(sys.argv[0])
+ " [--env] [-p <prefix>] -i <urdf-file> -o <blender-script>\n"
)
print("Arguments:")
print("\t-i urdf-file \t[mandatory]\tinput URDF file")
print("\t-o blender-script\t[mandatory]\toutput blender script")
print("\t-p prefix \t[optional] \tprefix of object names")
print("\t--env \t[optional] \tobject static transform will be ignored")
try:
opts, args = getopt.getopt (sys.argv[1:], "p:i:o:", ["env", "prefix=", "in=", "out="])
opts, args = getopt.getopt(
sys.argv[1:], "p:i:o:", ["env", "prefix=", "in=", "out="]
)
except getopt.GetoptError as err:
usage ()
print ("\nError: " + err.msg + "\n")
usage()
print("\nError: " + err.msg + "\n")
raise
prefix = ""
......@@ -39,19 +48,19 @@ for opt, arg in opts:
isEnv = True
if urdfFilename is None or blendFilename is None:
usage ()
sys.exit (2)
usage()
sys.exit(2)
## There is a bug in urdf_parser_py that has been fixed in a later release.
## See https://github.com/ros/urdfdom/pull/38
## and https://github.com/ros/urdfdom/pull/46/files
# There is a bug in urdf_parser_py that has been fixed in a later release.
# See https://github.com/ros/urdfdom/pull/38
# and https://github.com/ros/urdfdom/pull/46/files
for tag in urdf.JointLimit.XML_REFL.attributes:
if tag.var == 'upper' or tag.var == 'lower':
if tag.var == "upper" or tag.var == "lower":
tag.required = False
tag.default = 0
## There is something wrong with tag transmission
## This just disables it but it is not the right way of doing it.
# There is something wrong with tag transmission
# This just disables it but it is not the right way of doing it.
urdf.Transmission.XML_REFL.attributes = list()
urdf.Transmission.XML_REFL.vars = list()
urdf.Transmission.XML_REFL.required_attribute_names = list()
......@@ -61,34 +70,38 @@ urdf.Transmission.XML_REFL.element_map = dict()
urdf.Transmission.XML_REFL.scalarNames = list()
urdf.Transmission.XML_REFL.scalars = list()
robot = urdf.URDF.from_xml_file (urdfFilename)
robot = urdf.URDF.from_xml_file(urdfFilename)
def resolve_ros_path (path):
filename = resource_retriever.get_filename (path)
if filename.startswith ("file://"):
def resolve_ros_path(path):
filename = resource_retriever.get_filename(path)
if filename.startswith("file://"):
return filename[7:]
else:
print ("Path might not be understood by blender: " + filename)
print("Path might not be understood by blender: " + filename)
return filename
def updateFrameMessage ():
def updateFrameMessage():
callerframerecord = inspect.stack()[1]
info = inspect.getframeinfo(callerframerecord[0])
print ("Update function %s in script %s:%i" % (info.function, info.filename, info.lineno))
print(f"Update function {info.function} in script {info.filename}:{info.lineno}")
class CreateBlenderObject:
def __init__ (self, prefix, file):
self.run = dict ()
def __init__(self, prefix, file):
self.run = dict()
self.file = file
self.prefix = prefix
self.materials = list ()
self.textures = list ()
self.materials = list()
self.textures = list()
self.run[urdf.Cylinder] = self.handleCylinder
self.run[urdf.Box] = self.handleBox
self.run[urdf.Sphere] = self.handleSphere
self.run[urdf.Mesh] = self.handleMesh
self.writeCmd ("import bpy")
self.writeCmd ("""
self.writeCmd("import bpy")
self.writeCmd(
"""
taggedObjects = list()
def tagObjects ():
global taggedObjects
......@@ -103,122 +116,131 @@ def getNonTaggedObjects ():
def setParent (children, parent):
for child in children:
child.parent = parent
""")
"""
)
def setName (self, name):
def setName(self, name):
if isEnv:
self.writeCmd ("bpy.context.object.name = \"" + self.prefix + name + "_0\"")
self.writeCmd('bpy.context.object.name = "' + self.prefix + name + '_0"')
else:
self.writeCmd ("bpy.context.object.name = \"" + self.prefix + name + "_visual0\"")
def setupParent (self, name):
self.writeCmd ("bpy.ops.object.empty_add ()")
self.writeCmd ("empty = bpy.context.object")
self.writeCmd ("empty.name = \"" + self.prefix + name + "\"")
self.writeCmd ("currentObj.parent = empty")
def translate (self, position):
self.writeCmd ("currentObj.location = %s" % \
(position, ))
def rotate (self, rotation):
self.writeCmd ("currentObj.rotation_euler = %s" % \
(rotation, ))
def scale (self, scale):
self.writeCmd ("currentObj.scale = %s" % \
(scale, ))
def handleSphere (self, sphere):
print ("Untested feature: Sphere will be treated as icosphere")
self.writeCmd ("bpy.ops.mesh.primitive_ico_sphere_add (size=%s)" %\
(sphere.radius,))
self.writeCmd ("currentObj = bpy.context.object")
def handleBox (self, geometry):
self.writeCmd ("bpy.ops.mesh.primitive_cube_add ()")
self.writeCmd ("currentObj = bpy.context.object")
self.writeCmd ("currentObj.dimensions = %s" %\
(geometry.size, ))
def handleCylinder (self, geometry):
self.writeCmd ("bpy.ops.mesh.primitive_cylinder_add (radius=%s, depth=%s)" % \
(geometry.radius, geometry.length, ))
self.writeCmd ("currentObj = bpy.context.object")
def handleMesh (self, geometry):
self.writeCmd ("tagObjects()")
self.writeCmd(
'bpy.context.object.name = "' + self.prefix + name + '_visual0"'
)
def setupParent(self, name):
self.writeCmd("bpy.ops.object.empty_add ()")
self.writeCmd("empty = bpy.context.object")
self.writeCmd('empty.name = "' + self.prefix + name + '"')
self.writeCmd("currentObj.parent = empty")
def translate(self, position):
self.writeCmd(f"currentObj.location = {position}")
def rotate(self, rotation):
self.writeCmd(f"currentObj.rotation_euler = {rotation}")
def scale(self, scale):
self.writeCmd(f"currentObj.scale = {scale}")
def handleSphere(self, sphere):
print("Untested feature: Sphere will be treated as icosphere")
self.writeCmd(f"bpy.ops.mesh.primitive_ico_sphere_add (size={sphere.radius})")
self.writeCmd("currentObj = bpy.context.object")
def handleBox(self, geometry):
self.writeCmd("bpy.ops.mesh.primitive_cube_add ()")
self.writeCmd("currentObj = bpy.context.object")
self.writeCmd(f"currentObj.dimensions = {geometry.size}")
def handleCylinder(self, geometry):
self.writeCmd(
"bpy.ops.mesh.primitive_cylinder_add"
f"(radius={geometry.radius}, depth={geometry.length})"
)
self.writeCmd("currentObj = bpy.context.object")
def handleMesh(self, geometry):
self.writeCmd("tagObjects()")
extension = os.path.splitext(geometry.filename)[1]
if extension.lower() == '.dae':
command = "bpy.ops.wm.collada_import (filepath=\"%s\")"
elif extension.lower() == '.stl':
command = "bpy.ops.import_mesh.stl (filepath=\"%s\")"
if extension.lower() == ".dae":
command = 'bpy.ops.wm.collada_import (filepath="%s")'
elif extension.lower() == ".stl":
command = 'bpy.ops.import_mesh.stl (filepath="%s")'
else:
command = "bpy.ops.mesh.primitive_cube_add () # Failed to find loading method for %s"
print ("Extension %s of file %s is not know by the script" %\
(extension, geometry.filename, ))
updateFrameMessage ()
self.writeCmd (command % (resolve_ros_path (geometry.filename),))
self.writeCmd ("imported_objects = getNonTaggedObjects ()")
self.writeCmd ("print(imported_objects)")
self.writeCmd ("bpy.ops.object.empty_add ()")
self.writeCmd ("currentObj = bpy.context.object")
self.writeCmd ("setParent (imported_objects, currentObj)")
def writeCmd (self, command):
command = (
"bpy.ops.mesh.primitive_cube_add () "
"# Failed to find loading method for %s"
)
print(
f"Extension {extension} of file {geometry.filename} "
"is not know by the script"
)
updateFrameMessage()
self.writeCmd(command % (resolve_ros_path(geometry.filename),))
self.writeCmd("imported_objects = getNonTaggedObjects ()")
self.writeCmd("print(imported_objects)")
self.writeCmd("bpy.ops.object.empty_add ()")
self.writeCmd("currentObj = bpy.context.object")
self.writeCmd("setParent (imported_objects, currentObj)")
def writeCmd(self, command):
print(command, file=self.file)
def addMaterial (self, name, rgba):
self.writeCmd ("mat = bpy.data.materials.new(\"%s\")" % (name,))
self.writeCmd ("mat.diffuse_color = %s" % (rgba[0:3],))
self.writeCmd ("mat.alpha = %s" % (rgba[3],))
def addMaterial(self, name, rgba):
self.writeCmd(f'mat = bpy.data.materials.new("{name}")')
self.writeCmd(f"mat.diffuse_color = {rgba[0:3]}")
self.writeCmd(f"mat.alpha = {rgba[3]}")
self.materials.append(name)
def addTexture (self, name, filename):
self.writeCmd ("img = bpy.data.images.load (\"%s\")" % (filename,))
self.writeCmd ("cTex = bpy.data.textures.new(\"%s\", type='IMAGE')" % (name,))
self.writeCmd ("cTex.image = img")
self.writeCmd ("mat = bpy.data.materials.new(\"%s\")" % (name,))
self.writeCmd ("mtex = mat.texture_slots.add()")
self.writeCmd ("mtex.texture = cTex")
self.writeCmd ("mtex.texture_coords = 'ORCO'")
def addTexture(self, name, filename):
self.writeCmd(f'img = bpy.data.images.load ("{filename}")')
self.writeCmd(f"cTex = bpy.data.textures.new(\"{name}\", type='IMAGE')")
self.writeCmd("cTex.image = img")
self.writeCmd(f'mat = bpy.data.materials.new("{name}")')
self.writeCmd("mtex = mat.texture_slots.add()")
self.writeCmd("mtex.texture = cTex")
self.writeCmd("mtex.texture_coords = 'ORCO'")
self.textures.append(name)
self.materials.append(name)
def setMatOrText (self, name):
def setMatOrText(self, name):
if name in self.textures:
# self.writeCmd ("bpy.context.object.data.textures.append(bpy.data.textures[\"%s\"])" %\
# (name,))
pass
# self.writeCmd
# ("bpy.context.object.data.textures.append(bpy.data.textures[\"%s\"])" %\
# (name,))
pass
if name in self.materials:
self.writeCmd ("bpy.context.object.data.materials.append(bpy.data.materials[\"%s\"])" %\
(name,))
self.writeCmd(
"bpy.context.object.data.materials.append"
f'(bpy.data.materials["{name}"])'
)
def __call__ (self, link):
def __call__(self, link):
geometry = link.visual.geometry
if type(geometry) in self.run:
self.run[type(geometry)](geometry)
self.setName(link.name)
if link.visual.material is not None:
self.setMatOrText (link.visual.material.name)
self.setupParent (link.name)
self.setMatOrText(link.visual.material.name)
self.setupParent(link.name)
if link.visual.origin is not None:
self.translate (link.visual.origin.position)
self.rotate (link.visual.origin.rotation)
self.translate(link.visual.origin.position)
self.rotate(link.visual.origin.rotation)
# if link.visual.geometry.scale is not None:
# self.scale (link.visual.geometry.scale)
# pass
# self.scale (link.visual.geometry.scale)
# pass
else:
print ("Geometry " + str(type(geometry)) + " not supported")
updateFrameMessage ()
print("Geometry " + str(type(geometry)) + " not supported")
updateFrameMessage()
with open (blendFilename, "w+") as blendscript:
blend = CreateBlenderObject (prefix, blendscript)
with open(blendFilename, "w+") as blendscript:
blend = CreateBlenderObject(prefix, blendscript)
for m in robot.materials:
if m.color is not None:
blend.addMaterial (m.name, m.color.rgba)
blend.addMaterial(m.name, m.color.rgba)
if m.texture is not None:
blend.addTexture (m.name, resolve_ros_path (m.texture.filename))
blend.addTexture(m.name, resolve_ros_path(m.texture.filename))
for link in robot.links:
if link.visual is not None:
blend (link)
blend(link)
Subproject commit 7eca9ee6c9d1c4ee20eb82272e94f9d11642053a
Subproject commit 29c0eb4e659304f44d55a0389e2749812d858659
# Copyright (c) 2019 CNRS
# Author: Joseph Mirabel
# Copyright (c) 2019 CNRS Author: Joseph Mirabel
#
# This file is part of gepetto-viewer-corba.
# gepetto-viewer-corba is free software: you can redistribute it
# and/or modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either version
# 3 of the License, or (at your option) any later version.
# This file is part of gepetto-viewer-corba. gepetto-viewer-corba is free
# software: you can redistribute it and/or modify it under the terms of the GNU
# Lesser General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
#
# gepetto-viewer-corba 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 Lesser Public License for more details. You should have
# received a copy of the GNU Lesser General Public License along with
# gepetto-viewer-corba. If not, see
# gepetto-viewer-corba 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 Lesser Public License
# for more details. You should have received a copy of the GNU Lesser General
# Public License along with gepetto-viewer-corba. If not, see
# <http://www.gnu.org/licenses/>.
# {{{ Find Qt tags
SET(_use_qt_local_doc FALSE)
SET(qt_online_doc "http://doc.qt.io")
set(_use_qt_local_doc FALSE)
set(qt_online_doc "http://doc.qt.io")
IF(PROJECT_USE_QT4)
FIND_FILE(QT4_TAGFILE doc/html/qt.tags
if(PROJECT_USE_QT4)
find_file(
QT4_TAGFILE doc/html/qt.tags
HINTS / /usr
PATH_SUFFIXES share/qt4)
# Eventually, QT4_TAGFILE can be set manually to
# /usr/share/qt4/doc/html/qt.tags
# on ubuntu 16.04, from package qt4-doc-html
IF(QT4_TAGFILE)
IF(_use_qt_local_doc)
GET_FILENAME_COMPONENT(path_to_doc ${QT4_TAGFILE} DIRECTORY)
SET(QT_TAGFILES "\"${QT4_TAGFILE}=${path_to_doc}\"" PARENT_SCOPE)
ELSE(_use_qt_local_doc)
SET(QT_TAGFILES "\"${QT4_TAGFILE}=${qt_doc}/archives/qt-4.8\"" PARENT_SCOPE)
ENDIF(_use_qt_local_doc)
ENDIF()
ELSE(PROJECT_USE_QT4)
SET(_QT_TAGFILES)
# Available components
# qtcore qtgui qtwidgets qtnetwork qtsql
# qtprintsupport qttestlib qtconcurrent qdoc qtxml
FOREACH (component "core" "widgets" "gui" "network")
STRING(TOUPPER ${component} _up_comp)
FIND_FILE(QT5_${_up_comp}_TAGFILE doc/qt${component}/qt${component}.tags
# /usr/share/qt4/doc/html/qt.tags on ubuntu 16.04, from package qt4-doc-html
if(QT4_TAGFILE)
if(_use_qt_local_doc)
get_filename_component(path_to_doc ${QT4_TAGFILE} DIRECTORY)
set(QT_TAGFILES
"\"${QT4_TAGFILE}=${path_to_doc}\""
PARENT_SCOPE)
else(_use_qt_local_doc)
set(QT_TAGFILES
"\"${QT4_TAGFILE}=${qt_doc}/archives/qt-4.8\""
PARENT_SCOPE)
endif(_use_qt_local_doc)
endif()
else(PROJECT_USE_QT4)
set(_QT_TAGFILES)
# Available components qtcore qtgui qtwidgets qtnetwork qtsql qtprintsupport
# qttestlib qtconcurrent qdoc qtxml
foreach(component "core" "widgets" "gui" "network")
string(TOUPPER ${component} _up_comp)
find_file(
QT5_${_up_comp}_TAGFILE doc/qt${component}/qt${component}.tags
HINTS / /usr
PATH_SUFFIXES share/qt5)
# Eventually, QT5_${_up_comp}_TAGFILE can be set manually to
# /usr/share/qt5/doc/qt${component}/qt${component}.tags
# on ubuntu 16.04, from package qtbase5-doc-html
IF(QT5_${_up_comp}_TAGFILE)
IF(_use_qt_local_doc)
GET_FILENAME_COMPONENT(path_to_doc ${QT5_${_up_comp}_TAGFILE} DIRECTORY)
SET(_QT_TAGFILES "${_QT_TAGFILES} \"${QT5_${_up_comp}_TAGFILE}=${path_to_doc}\"")
ELSE(_use_qt_local_doc)
SET(_QT_TAGFILES "${_QT_TAGFILES} \"${QT5_${_up_comp}_TAGFILE}=${qt_doc}/qt-5\"")
ENDIF(_use_qt_local_doc)
ENDIF()
ENDFOREACH()
SET(QT_TAGFILES ${_QT_TAGFILES} PARENT_SCOPE)
ENDIF(PROJECT_USE_QT4)
# /usr/share/qt5/doc/qt${component}/qt${component}.tags on ubuntu 16.04,
# from package qtbase5-doc-html
if(QT5_${_up_comp}_TAGFILE)
if(_use_qt_local_doc)
get_filename_component(path_to_doc ${QT5_${_up_comp}_TAGFILE} DIRECTORY)
set(_QT_TAGFILES
"${_QT_TAGFILES} \"${QT5_${_up_comp}_TAGFILE}=${path_to_doc}\"")
else(_use_qt_local_doc)
set(_QT_TAGFILES
"${_QT_TAGFILES} \"${QT5_${_up_comp}_TAGFILE}=${qt_doc}/qt-5\"")
endif(_use_qt_local_doc)
endif()
endforeach()
set(QT_TAGFILES
${_QT_TAGFILES}
PARENT_SCOPE)
endif(PROJECT_USE_QT4)
# }}}
# vim: foldmethod=marker foldlevel=0
FILE_PATTERNS = *.idl *.hh *.py *.idl *.dox
FILE_PATTERNS = *.idl *.hh *.py *.idl
INPUT = @CMAKE_SOURCE_DIR@/include \
@CMAKE_SOURCE_DIR@/idl \
@CMAKE_SOURCE_DIR@/plugins \
@CMAKE_SOURCE_DIR@/pyplugins \
@CMAKE_SOURCE_DIR@/src/gepetto\
@CMAKE_SOURCE_DIR@/doc
EXAMPLE_PATH = @CMAKE_SOURCE_DIR@/examples
LAYOUT_FILE = @CMAKE_SOURCE_DIR@/doc/layout.xml
TAGFILES += @QT_TAGFILES@
/**
\defgroup gepetto_viewer_corba_cmake_macros CMake macros
\{
\brief
Here are some details on how to build and install plugins with CMake.
\par Get the macro.
\code{.py}
# Tells pkg-config to read qtversion and cmake_plugin from pkg config file.
LIST(APPEND PKG_CONFIG_ADDITIONAL_VARIABLES qtversion cmake_plugin)
ADD_REQUIRED_DEPENDENCY("gepetto-viewer-corba")
# Variable GEPETTO_VIEWER_CORBA_QTVERSION contains something like 4.8.1 or 5.2.1
# Include macro GEPETTO_GUI_PLUGIN
INCLUDE(${GEPETTO_VIEWER_CORBA_PREFIX}/${GEPETTO_VIEWER_CORBA_CMAKE_PLUGIN})
\endcode
\par Declare a C++ plugin:
\code{.py}
GEPETTO_GUI_PLUGIN(pluginskeleton
# Use this option only if you write a plugin within
# gepetto-viewer-corba package
# INSIDE_GEPETTO_VIEWER_CORBA
# Whether to use Qt4 or Qt5
${QT4}
# List of headers that need not to be moced.
HEADERS_NO_MOC
# List of headers to be moced
HEADERS
plugin.hh
# List of Qt forms
FORMS
# List of Qt resources
RESOURCES
# List of source files
SOURCES
plugin.cc
# List of dependencies to be given to TARGET_LINK_LIBRARIES
LINK_DEPENDENCIES
# List of dependencies to be given to PKG_CONFIG_USE_DEPENDENCY
PKG_CONFIG_DEPENDENCIES
)
\endcode
\par Declare a Python plugin:
\code{.py}
# pythonfile refers to the path to .py file from a
# path of PYTHONPATH
GEPETTO_GUI_PYPLUGIN (pythonfile)
\endcode
\}
*/
<br><br>
<hr>
<center>
<img src="$relpath$pictures/footer.jpg" height="100" alt="footer">
<br>hpp-corbaserver library documentation</br>
</center>
<hr>
</center>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>$title</title>
<link href="$relpath$tabs.css" rel="stylesheet" type="text/css">
<link href="$relpath$doxygen.css" rel="stylesheet" type="text/css">
</head><body>