著者アーカイブ yamamoto.yosuke

著者:yamamoto.yosuke

SwitchBot を ROS から利用する – コマンド操作編2

本シリーズ前回の記事 SwitchBot を ROS から利用する – コマンド操作編1 では SwitchBot を ROS から利用する switchbot_ros の導入とサンプル Python コードの実行の様子を紹介しました.

今回は前回の記事の続きとしてサンプルのソースコードで扱われていた SwitchBot デバイス以外のものを ROS から操作するために SwitchBot API のコマンドセットを調べて control_switchbot.py に実装する過程について紹介します.

  1. 利用可能な SwitchBot デバイス情報の取得
  2. SwitchBot デバイス API コマンドセットの調査
  3. SwitchBot デバイスコマンドのソースコード追記

利用可能な SwitchBot デバイス情報の取得

switchbot_ros のサンプル Python コード control_switchbot.py においてボット(スイッチ)をオンにする命令は次のようになっていて「デバイス名」とそれに対する「コマンド」の2つを指定する必要があります.

client.control_device('bot74a', 'turnOn')
client.control_device('デバイス名', 'コマンド')

このうち「コマンド」は SwitchBot API にてデバイスタイプごとに設定されているので「デバイスタイプ」が何かを知る必要があります.

ユーザのアカウントで登録されているデバイスの「デバイス名」と「デバイスタイプ」は switchbot.launch を実行すると表示されます.

(下記 launch オプションの YOUR_TOKEN と YOUR_SECRET をそれぞれユーザアカウントのトークンとシークレットに置き換えて実行)

switchbot.launch 実行入力

$ source ~/switchbot_ws/devel/setup.bash
$ roslaunch switchbot_ros switchbot.launch token:=YOUR_TOKEN secret:=YOUR_SECRET

switchbot.launch 実行出力例

... logging to /home/robotuser/.ros/log/87b6e5c8-c1a2-11ee-bce7-1d89a9d14e1f/roslaunch-robotuser-PC-62866.log
Checking log directory for disk usage. This may take a while.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://robotuser-PC:40731/

SUMMARY
========

PARAMETERS
 * /rosdistro: noetic
 * /rosversion: 1.16.0
 * /switchbot_ros/secret: (シークレットの上位数桁が表示)...
 * /switchbot_ros/token: (トークンの上位数桁が表示)...

NODES
  /
    switchbot_ros (switchbot_ros/switchbot_ros_server.py)

auto-starting new master
process[master]: started with pid [62874]
ROS_MASTER_URI=http://localhost:11311

setting /run_id to 87b6e5c8-c1a2-11ee-bce7-1d89a9d14e1f
process[rosout-1]: started with pid [62884]
started core service [/rosout]
process[switchbot_ros-2]: started with pid [62891]
[INFO] [1706861436.195243]: Switchbot API Client initialized.
[INFO] [1706861436.199678]: Using SwitchBot API v1.1
[INFO] [1706861436.204957]: Switchbot Device List:
6 Item(s)
deviceName: bot74a, deviceID: (固有のID番号が表示), deviceType: Bot
deviceName: hub2a, deviceID: (固有のID番号が表示), deviceType: Hub 2
deviceName: plugmini7a1, deviceID: (固有のID番号が表示), deviceType: Plug Mini (JP)
deviceName: remote-button10a, deviceID: (固有のID番号が表示), deviceType: Remote
deviceName: tapelight7a1, deviceID: (固有のID番号が表示), deviceType: Strip Light
deviceName: thermo-hygrometer-f7a, deviceID: (固有のID番号が表示), deviceType: Meter

[INFO] [1706861436.208853]: Switchbot Remote List:
2 Item(s)
deviceName: air-conditioner, deviceID: (固有のID番号が表示), remoteType: Air Conditioner
deviceName: pendant-light, deviceID: (固有のID番号が表示), remoteType: DIY Light

[INFO] [1706861436.214168]: Switchbot Scene List:
3 Item(s)
sceneName: turnoff-all-lights, sceneID: (固有のID番号が表示)
sceneName: turnon-all-lights, sceneID: (固有のID番号が表示)
sceneName: turnon-all-lights, sceneID: (固有のID番号が表示)

[INFO] [1706861436.254126]: Ready.

switchbot.launch 実行出力から次の1行を例にとると「デバイス名」が plugmini7a1 で「デバイスタイプ」が Plug Mini (JP) です.

deviceName: plugmini7a1, deviceID: (固有のID番号が表示), deviceType: Plug Mini (JP)

SwitchBot デバイス API コマンドセットの調査

操作したい SwitchBot デバイスタイプが分かればそのコマンドセットを調べます.SwitchBot API のコマンドセットは下記の Web ページで知ることができます.

今回はデバイスタイプ Plug Mini (JP) と Strip Light のデバイスを操作したいのでそれらのコマンドセットについて調べます.

Plug Mini (JP) のコマンドセット

電源プラグの On/Off を行う SwitchBot デバイスである Plug Mini (JP) のコマンドセットの説明は次のリンク先にあります.

上記 Web ページの Plug Mini (JP) コマンドセットの表をそのまま貼り付けたものが次の表です.

deviceType commandType Command command parameter Description
Plug Mini (JP) command turnOn default set to ON state
Plug Mini (JP) command turnOff default set to OFF state
Plug Mini (JP) command toggle default toggle state

デバイスの機能どおりに電源入 turnOn,電源切 turnOff,電源入切の切替 toggle の3つのコマンドにより構成されています.

Strip Light のコマンドセット

テープライト形状の SwitchBot デバイスである Strip Light のコマンドセットの説明は次のリンク先にあります.

上記 Web ページの Strip Light コマンドセットの表をそのまま貼り付けたものが次の表です.

deviceType commandType Command command parameter Description
Strip Light command turnOn default set to ON state
Strip Light command turnOff default set to OFF state
Strip Light command toggle default toggle state
Strip Light command setBrightness {1-100} set brightness
Strip Light command setColor "{0-255}:{0-255}:{0-255}" set RGB color value

点灯 turnOn,消灯 turnOff,明滅切替 toggle,輝度設定 setBrightness,色設定 setColor の5つのコマンドにより構成されていて,そのうち輝度設定では{1-100} の範囲で輝度設定, "{0-255}:{0-255}:{0-255}" の値で RGB 色設定を行います.

SwitchBot デバイスコマンドのソースコード追記

Plug Mini (JP) を操作するソースコード追記と実行

デバイス名 plugmini7a1 の Plug Mini (JP) を操作します. ROS からコマンドを送って On/Off の切り替え toggle をしてみます.

サンプルコード control_switchbot.py に client.control_device('plugmini7a1', 'toggle') を追加します.(下記ソースコードの 16行目)

control_switchbot.py

#!/usr/bin/env python

import rospy
from switchbot_ros.switchbot_ros_client import SwitchBotROSClient

rospy.init_node('controler_node')
client = SwitchBotROSClient()

devices = client.get_devices()
print(devices)

# client.control_device('pendant-light', 'turnOn')

# client.control_device('bot74a', 'turnOn')

client.control_device('plugmini7a1', 'toggle')

ここでは元々サンプルコードにあったペンダントライトとボット(スイッチ)の操作をする行(上記ソースコードの12,14行目)は行頭に # を入れてコメントアウトして実行されないようにしています.

変更を加えた control_switchbot.py ファイルを保存してから実行します.

ターミナル 1 : switchbot.launch 実行入力

$ source ~/switchbot_ws/devel/setup.bash
$ roslaunch switchbot_ros switchbot.launch token:=YOUR_TOKEN secret:=YOUR_SECRET

ターミナル 2 : control_switchbot.py 実行入力

$ source ~/switchbot_ws/devel/setup.bash
$ rosrun switchbot_ros control_switchbot.py 

Strip Light を操作するソースコード追記と実行

デバイス名 tapelight7a1 の Strip Light (テープライト)を操作します. ROS からコマンドを送って次の動作をしてみます.

  1. 消灯
  2. 点灯
  3. 輝度を 100% に設定
  4. 色を白 '255:255:255' に設定
  5. 色を赤 '255:0:0' に設定
  6. 色を緑 '0:255:0' に設定
  7. 色を青 '0:0:255' に設定
  8. 輝度を 1% に設定
  9. 消灯

サンプルコード control_switchbot.py に下記ソースコードの18行目以降を追加します.

値を設定する setBrightnesssetColor といったコマンドでは各数値を control_device() の引数 parameter に文字列として渡します.

また control_device() の中ではコマンドを Action サーバにゴールとして送っているので新しいコマンドが前のコマンドに置き換わらないように1つ1つのコマンド実行を終えるのを待つように引数 waitTrue を渡しています.

control_switchbot.py

#!/usr/bin/env python

import rospy
from switchbot_ros.switchbot_ros_client import SwitchBotROSClient

rospy.init_node('controler_node')
client = SwitchBotROSClient()

devices = client.get_devices()
print(devices)

# client.control_device('pendant-light', 'turnOn')

# client.control_device('bot74a', 'turnOn')

# client.control_device('plugmini7a1', 'toggle')

client.control_device('tapelight7a1', 'turnOff', wait=True)
client.control_device('tapelight7a1', 'turnOn', wait=True)
client.control_device('tapelight7a1', 'setBrightness', parameter='100', wait=True)
client.control_device('tapelight7a1', 'setColor', parameter='255:255:255', wait=True)
client.control_device('tapelight7a1', 'setColor', parameter='255:0:0', wait=True)
client.control_device('tapelight7a1', 'setColor', parameter='0:255:0', wait=True)
client.control_device('tapelight7a1', 'setColor', parameter='0:0:255', wait=True)
client.control_device('tapelight7a1', 'setBrightness', parameter='1', wait=True)
client.control_device('tapelight7a1', 'turnOff', wait=True)

前述の Plug Mini (JP) のときと同様に変更を加えた control_switchbot.py ファイルを保存してから実行します.

補足ソースコード変更

筆者が試した範囲では時々 client.control_device() が実行されない不具合が見受けられ,それが SwitchBotROSClient インスタンス作成時 client = SwitchBotROSClient() に Action サーバの起動やサーバへの接続が不十分であることが原因のように思われました.

そこで下記の switchbot_ros_client.py の21行目のように self.action_client.wait_for_server() を入れて Action サーバが起動して接続されるのを待つようにしたところ,現状では安定して client.control_device() が実行されているように感じます.

switchbot_ros_client.py

import rospy
import actionlib
from switchbot_ros.msg import SwitchBotCommandAction
from switchbot_ros.msg import SwitchBotCommandGoal
from switchbot_ros.msg import DeviceArray


class SwitchBotROSClient(object):

    def __init__(self,
                 actionname='switchbot_ros/switch',
                 topicname='switchbot_ros/devices'):

        self.actionname = actionname
        self.topicname = topicname
        self.action_client = actionlib.SimpleActionClient(
                actionname,
                SwitchBotCommandAction
                )
        rospy.loginfo("Waiting for action server to start.")
        self.action_client.wait_for_server()

    def get_devices(self, timeout=None):

        return rospy.wait_for_message(
                self.topicname,
                DeviceArray,
                timeout=timeout
                )

    def control_device(self,
                       device_name,
                       command,
                       parameter='',
                       command_type='',
                       wait=False
                       ):

        goal = SwitchBotCommandGoal()
        goal.device_name = device_name
        goal.command = command
        goal.parameter = parameter
        goal.command_type = command_type
        self.action_client.send_goal(goal)
        if wait:
            self.action_client.wait_for_result()
            return self.action_client.get_result()

今回の記事はここまでです.

著者:yamamoto.yosuke

SwitchBot を ROS から利用する – コマンド操作編1

本記事では SwitchBot を ROS から利用できるソフトウェア switchbot_ros の使い方を紹介します.


SwitchBot は多くの IoT スマートホームデバイス製品を提供しているブランドで,既にそれらを日常生活の中で活用されている方も多いのではないでしょうか.

SwitchBot からはソフトウェアインターフェースとして WebAPI が提供されていて,2024年2月はじめの時点での最新バージョンが v1.1 となっています.

SwitchBot の WebAPI を ROS から利用する switchbot_ros は下記のリポジトリで公開されていて SwitchBot API v1.1 にも対応しています.

SwitchBot の機器を ROS のシステムに組み込むことによりロボットとスマートホームデバイスを組み合わせた動作システムを簡単に実現することが可能となります.


今回は switchbot_ros を含む jsk_3rdparty リポジトリを Ubuntu PC 内の ROS ワークスペースにクローン・ビルドして ROS から SwitchBot デバイスを動作させます.

switchbot_ros のビルド

本記事では次の環境で switchbot_ros を利用しています.

  • Ubuntu 20.04
  • ROS Noetic

Ubuntu や ROS のインストールが済んだ状態で次のように switchbot_ros を利用するためのワークスペースを作成して jsk_3rdparty リポジトリをクローンしてビルドします.

$ source /opt/ros/noetic/setup.bash
$ mkdir -p ~/switchbot_ws/src
$ cd ~/switchbot_ws
$ catkin build 
$ source ~/switchbot_ws/devel/setup.bash
$ cd ~/switchbot_ws/src
$ git clone https://github.com/jsk-ros-pkg/jsk_3rdparty.git
$ cd ~/switchbot_ws
$ rosdep install -y -r --from-paths src --ignore-src
$ catkin build
$ source ~/switchbot_ws/devel/setup.bash

SwitchBot API のトークンとシークレットの取得

switchbot_ros は SwitchBot API を利用していますので SwitchBot API にアクセスするための SwitchBot アカウント各々に固有の「トークン(token)」と「シークレット(secret)」の2つの情報が必要になります.

SwitchBot Magazine – 【API】新バージョンAPI v1.1を公開しました にトークンとシークレットの取得方法などが書かれています.この記事から引用・まとめをするとトークンとシークレットの取得方法はつぎのようになっています.

  1. App Store または Google Play Store より SwitchBot アプリをダウンロード
  2. SwitchBot アカウントを作成またはサインイン
  3. オープントークンを生成
    1. 「プロフィールページ」 → 「設定」へ移動
    2. 「アプリバージョン」を10回タップ → 「開発者向けオプション」が表示される
    3. 「開発者向けオプション」をタップ
    4. 「トークン」と「クライアントシークレット」をコピーしてテキストとして保存

switchbot_ros の実行

switchbot_ros の中にあるコマンドを発する Python コード例 control_switchbot.py を実行して ROS から SwitchBot デバイスのハブの赤外線リモコン発信で部屋の電気を点灯した後にボット(スイッチ)を動作させます.

control_switchbot.py の中身はシンプルで 12行目 で照明を点灯させて,14行目 でボット(スイッチ)を On しています.

control_switchbot.py

#!/usr/bin/env python

import rospy
from switchbot_ros.switchbot_ros_client import SwitchBotROSClient

rospy.init_node('controler_node')
client = SwitchBotROSClient()

devices = client.get_devices()
print(devices)

client.control_device('pendant-light', 'turnOff')

client.control_device('bot74a', 'turnOn')
12行目で client.control_device('pendant-light', 'turnOff')'turnOff' となっているのに点灯?と思いますが筆者のペンダントライトのリモコンを SwitchBot アプリで登録する際に電源の On/Off ボタンが1つのものとして扱われていて 'turnOn''turnOff' も On/Off の切り替えボタンとして機能してしまっているためです. ( = もう一度 client.control_device('pendant-light', 'turnOff') を実行すると消灯になる) このように特に登録するリモコンについてはどのようにマッピングができたかに挙動が依存するのでコマンドに対する各々のデバイスの挙動を確認してから利用する必要があります.

操作コマンドの SwitchBot デバイスに至るまでの大まかな流れは次のようになっています.

  • control_switchbot.py
  • → SwitchBot ROS Client
  • → SwitchBot ROS Action Server
  • → SwitchBot WebAPI
  • → SwitchBot デバイス

それでは実際に実行してみます. SwitchBot ROS アクションサーバを起動してから別ターミナルで control_switchbot.py を実行します.

ターミナル 1 : SwitchBot ROS アクションサーバの起動

下記コマンドの SwichBot ROS アクションサーバの起動実行時に launch オプションの token:=YOUR_TOKENYOUR_TOKEN を SwitchBot アプリで取得したトークンに置き換えて, secret:=YOUR_SECRETYOUR_SECRET を取得したシークレットに置き換えて書いて実行します.

switchbot.launch 実行入力

$ source ~/switchbot_ws/devel/setup.bash
$ roslaunch switchbot_ros switchbot.launch token:=YOUR_TOKEN secret:=YOUR_SECRET

switchbot.launch 実行出力例

... logging to /home/robotuser/.ros/log/87b6e5c8-c1a2-11ee-bce7-1d89a9d14e1f/roslaunch-robotuser-PC-62866.log
Checking log directory for disk usage. This may take a while.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://robotuser-PC:40731/

SUMMARY
========

PARAMETERS
 * /rosdistro: noetic
 * /rosversion: 1.16.0
 * /switchbot_ros/secret: (シークレットの上位数桁が表示)...
 * /switchbot_ros/token: (トークンの上位数桁が表示)...

NODES
  /
    switchbot_ros (switchbot_ros/switchbot_ros_server.py)

auto-starting new master
process[master]: started with pid [62874]
ROS_MASTER_URI=http://localhost:11311

setting /run_id to 87b6e5c8-c1a2-11ee-bce7-1d89a9d14e1f
process[rosout-1]: started with pid [62884]
started core service [/rosout]
process[switchbot_ros-2]: started with pid [62891]
[INFO] [1706861436.195243]: Switchbot API Client initialized.
[INFO] [1706861436.199678]: Using SwitchBot API v1.1
[INFO] [1706861436.204957]: Switchbot Device List:
6 Item(s)
deviceName: bot74a, deviceID: (固有のID番号が表示), deviceType: Bot
deviceName: hub2a, deviceID: (固有のID番号が表示), deviceType: Hub 2
deviceName: plugmini7a1, deviceID: (固有のID番号が表示), deviceType: Plug Mini (JP)
deviceName: remote-button10a, deviceID: (固有のID番号が表示), deviceType: Remote
deviceName: tapelight7a1, deviceID: (固有のID番号が表示), deviceType: Strip Light
deviceName: thermo-hygrometer-f7a, deviceID: (固有のID番号が表示), deviceType: Meter

[INFO] [1706861436.208853]: Switchbot Remote List:
2 Item(s)
deviceName: air-conditioner, deviceID: (固有のID番号が表示), remoteType: Air Conditioner
deviceName: pendant-light, deviceID: (固有のID番号が表示), remoteType: DIY Light

[INFO] [1706861436.214168]: Switchbot Scene List:
3 Item(s)
sceneName: turnoff-all-lights, sceneID: (固有のID番号が表示)
sceneName: turnon-all-lights, sceneID: (固有のID番号が表示)
sceneName: turnon-all-lights, sceneID: (固有のID番号が表示)

[INFO] [1706861436.254126]: Ready.

ターミナル 2 : control_switchbot.py の実行

ターミナル1 の switchbot.launch を起動したままの状態で別のターミナルで control_switchbot.py を実行します.

control_switchbot.py 実行入力

$ source ~/switchbot_ws/devel/setup.bash
$ rosrun switchbot_ros control_switchbot.py 

control_switchbot.py 実行出力例

devices: 
  - 
    name: "bot74a"
    type: "Bot"
  - 
    name: "hub2a"
    type: "None"
  - 
    name: "plugmini7a1"
    type: "Plug Mini (JP)"
  - 
    name: "remote-button10a"
    type: "Remote"
  - 
    name: "tapelight7a1"
    type: "Strip Light"
  - 
    name: "thermo-hygrometer-f7a"
    type: "Meter"
  - 
    name: "air-conditioner"
    type: "Air Conditioner"
  - 
    name: "pendant-light"
    type: "DIY Light"

control_switchbot.py 実行出力時の SwitchBot デバイス動作の様子

SwitchBot を ROS から操作する感じが伝わりましたでしょうか?

今回の記事はここまでです.


本シリーズ次回の記事では今回実行した Python コード例で扱われていたもの以外の SwitchBot デバイスを ROS から操作するために SwitchBot API を調べて control_switchbot.py にコマンドを追加する様子についてお伝えする予定です.

著者:yamamoto.yosuke

ChatGPT と ROS – ROS Topic を介した ChatGPT チャットプログラム

本シリーズ前回の記事 1. ROS Service プログラムの文脈をふまえたチャット対応 では OpenAI の Chat Completion API を利用して過去のチャット履歴もふまえたチャットを行える ROS Service プログラムを作成した様子をお伝えしました.

今回の記事では Chat Completion API を利用した「文脈をふまえたチャット」をする ROS ソフトウェアを実装してみた2つ目の方法「2. ROS Topic を介した ChatGPT チャットプログラム」を作成した様子を紹介します.

  1. ROS Service プログラムの文脈をふまえたチャット対応
    • 「1問1答」形式 →「チャット」形式
  2. ROS Topic を介した ChatGPT チャットプログラム
    • ROS Service の応答 → ROS Topic のやり取りによる ChatGPT とのチャット

ROS Topic を介した ChatGPT のチャットプログラム

前回,比較的短文のチャットを扱う Chat Completion API へのアクセスであれば ROS Service よりも ROS Topic を介したメッセージのやり取りの方が ROS ノード内でのチャット会話に限られず,より ROS に親和的でよりシンプルな構成になるのでは?という反省がありました.

今回の ROS Topic を用いたチャットプログラムの作成方針は次のようにしました.

  • Chat Completion API にアクセスする ROS Node
    • ROS Topic /request を購読してユーザの発言を得る
    • ユーザの発言をふまえて Chat Completion API にアクセスして応答を ROS Topic /response としてパブリッシュする
    • チャットの履歴データを蓄積する
  • チャットユーザ側
    • 質問を ROS Topic /request にパブリッシュする
    • 返答は ROS Topic /response を購読して得る

ROS Service プログラムの場合はチャット履歴をふまえたとしてもチャット機能提供側とユーザとの1者対1者でのやり取りでしたが,ROS Topic にすることでチャット機能提供側と複数のユーザの1者対他者でのやり取りも可能になる利点もあります.

ソースコード

ROS Topic を介した文脈をふまえたチャットプログラムで追加したファイルは次の2つです.

  • scripts / openai_chat_rostopic.py
  • launch / openai_chat.launch

サービスの定義など考慮しなくて良いので非常にシンプルです.

以下,それぞれのファイル内のコードを記載して少し説明をします.

scripts / openai_chat_rostopic.py

#!/usr/bin/env python3

import rospy
import openai

from std_msgs.msg import String


class Chatter:
    """
    Chat with ChatGPT on ROS topics
    """
    def __init__(self):
        # Get ROS parameters
        prompt = rospy.get_param('~prompt')
        self.model = rospy.get_param('~model')
        openai.api_key = rospy.get_param('~key')
        
        rospy.loginfo("For \'system\': %s" % (prompt))
        
        # Set initial message with a prompt
        self.messages = []
        self.messages.append({"role": "system", "content": str(prompt)})
        
        self.sub = rospy.Subscriber('request', String, self.callback)
        self.pub = rospy.Publisher('response', String, queue_size=10)
        
        rospy.spin()


    def callback(self, data):
        rospy.loginfo("request: %s", data.data)
        
        # Add user's input to the history
        self.messages.append({"role": "user", "content": str(data.data)})
        
        response = openai.ChatCompletion.create(
            model=self.model,
            messages=self.messages
        )
        
        content = response["choices"][0]["message"]["content"]
        role = response["choices"][0]["message"]["role"]
        token = response["usage"]["total_tokens"]
        
        # Add GPT's response to the history
        self.messages.append({"role": str(role), "content": str(content)})
        
        rospy.loginfo("%s(token:%d): %s" % (role, token, content))
        self.pub.publish(content)


if __name__ == "__main__":
    rospy.init_node('chat_rostopic', anonymous=True)
    chatter = Chatter()
    
  • L25,31: ROS Topic /request を購読(Subscribe)してトピックを受け取ったら callback() メソッドを呼び出す
  • L35: callback() メソッド内で新たなリクエストをチャット履歴に追加
  • L37-40: Chat Completion API に投げる
  • L47: Chat Completion API からの返答をチャット履歴に追加
  • ROS Topic /response としてパブリッシュ

launch / openai_chat.launch

launch オプション service を用いて前回の記事で紹介した ROS Service によるチャットプログラムと今回の ROS Topic を介したチャットプログラムのどちらを実行するかを切り替えるようにしています.

<launch>
  
  <arg name="key" default="$(env OPENAI_API_KEY)" />
  <arg name="model" default="gpt-3.5-turbo" />
  <arg name="service" default="false" />
  <arg name="prompt" default="You are a helpful assistant." />
  
  <node if="$(arg service)"
        pkg="openai_ros" type="openai_chat_server.py" name="openai_chat_service" output="screen">
    <param name="key" value="$(arg key)" />
    <param name="model" value="$(arg model)" />
  </node>
  
  <node unless="$(arg service)"
        pkg="openai_ros" type="openai_chat_rostopic.py" name="openai_chat_topic" output="screen">
    <param name="key" value="$(arg key)" />
    <param name="model" value="$(arg model)" />
    <param name="prompt" value="$(arg prompt)" />
  </node>
  
</launch>

実行例

文脈をふまえた ROS Topic を介したチャットプログラムを実行した例を以下に記載します.

ターミナル 1 : チャットノードの起動

Chat Completion API にアクセスして ROS Topic でやり取りする ROS Node を openai_chat.launch で起動しています.

robotuser@robotuser-PC:~$ source ~/openai_ws/devel/setup.bash
robotuser@robotuser-PC:~$ export OPENAI_API_KEY="sk-..."
robotuser@robotuser-PC:~$ roslaunch openai_ros openai_chat.launch 
... logging to /home/robotuser/.ros/log/609f8d12-52cd-11ee-9968-6b3ff6703622/roslaunch-robotuser-PC-5035.log
Checking log directory for disk usage. This may take a while.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://robotuser-PC:40257/

SUMMARY
========

PARAMETERS
 * /openai_chat/key: sk-3JDluBbxsNuIhi...
 * /openai_chat/model: gpt-3.5-turbo
 * /openai_chat/prompt: You are a helpful...
 * /rosdistro: noetic
 * /rosversion: 1.16.0

NODES
  /
    openai_chat (openai_ros/openai_chat_rostopic.py)

auto-starting new master
process[master]: started with pid [5043]
ROS_MASTER_URI=http://localhost:11311

setting /run_id to 609f8d12-52cd-11ee-9968-6b3ff6703622
process[rosout-1]: started with pid [5053]
started core service [/rosout]
process[openai_chat-2]: started with pid [5060]
[INFO] [1694675254.662674]: For 'system': You are a helpful assistant.
[INFO] [1694675266.017788]: request: Hello
[INFO] [1694675267.401076]: assistant(token:27): Hello! How can I assist you today?
[INFO] [1694675292.897892]: request: インターネットはどこにありますか?
[INFO] [1694675300.168180]: assistant(token:183): インターネットは物理的な場所にあるのではなく,世界中のコンピューターがネットワークで結ばれている仮想的な空間です.インターネットは,データを送信し受け取るためのプロトコルとパケット交換技術を使用しています.つまり,インターネットは世界中のコンピューターネットワークの集合体です.
[INFO] [1694675347.975610]: request: 世界中とは地球のことですか?
[INFO] [1694675354.647098]: assistant(token:324): はい,正確に言えば,インターネットは地球上のコンピューターネットワークの集合体です.このネットワークには,地球上の様々な場所に設置されたサーバーやルーターなどの機器が含まれます.インターネットのデータ通信は,これらの機器を介して行われます.
[INFO] [1694675387.510775]: request: あなたは誰ですか?
[INFO] [1694675391.409440]: assistant(token:407): 私はAI(人工知能)ベースのヘルプフルなアシスタントです.質問や問題解決,情報の提供など,お手伝いできることがありましたらお知らせください.
[INFO] [1694675436.047036]: request: どのようにヘルプフルですか?
[INFO] [1694675447.019485]: assistant(token:645): 私は多くの情報と知識を持つAIですので,様々な質問や問題に対してお答えすることができます.例えば,旅行の予約や天気予報の確認,翻訳や研究のサポート,一般的な知識の提供など,さまざまな分野でお手伝いすることができます.また,自然言語処理の技術を利用して,あなたの質問や要求を理解し,最適な回答やソリューションを提供することも可能です.お困りのことや疑問があれば,いつでもお気軽にお知らせください.
[INFO] [1694675485.687087]: request: 東京の明日の天気もわかりますか?
[INFO] [1694675507.944942]: assistant(token:1064): もちろんです!東京の天気予報を調べてみましょう.

私はリアルタイムのデータにアクセスできるわけではありませんが,一般的に天気予報を提供する公式のウェブサイトやアプリを利用して,詳細な天気予報を確認することができます.天気予報は頻繁に更新されるため,事前に確認することをおすすめします.以下は一般的な天気予報サービスの利用方法です.

- インターネット検索エンジンで「東京の天気予報」と検索すると,現在の天気情報と明日の天気予報を含む結果が表示されます.
- スマートフォンやタブレットを使用している場合は,天気予報を提供するアプリをダウンロードしてインストールすることもできます.定番のアプリには「Weather」や「Weather Underground」などがあります.

これらの方法を使用して,明日の東京の天気を確認してみてください.天気予報に関する詳細な情報を入手するためには,地元の気象庁や天気予報サービスの公式ウェブサイトを参照することもおすすめです.
^C[openai_chat-2] killing on exit
[rosout-1] killing on exit
[master] killing on exit
shutting down processing monitor...
... shutting down processing monitor complete
done
robotuser@robotuser-PC:~$ 

ターミナル 2 : ROS Topic に発言をパブリッシュ

1つ目のターミナルで openai_chat.launch を起動したままの状態で2つ目のターミナルから ROS Topic をパブリッシュします.

robotuser@robotuser-PC:~$ rostopic pub -1 /request std_msgs/String "Hello"
publishing and latching message for 3.0 seconds
robotuser@robotuser-PC:~$ rostopic pub -1 /request std_msgs/String "インターネットはどこにありますか ?"
publishing and latching message for 3.0 seconds
robotuser@robotuser-PC:~$ rostopic pub -1 /request std_msgs/String "世界中とは地球のことですか?"
publishing and latching message for 3.0 seconds
robotuser@robotuser-PC:~$ rostopic pub -1 /request std_msgs/String "あなたは誰ですか?"
publishing and latching message for 3.0 seconds
robotuser@robotuser-PC:~$ rostopic pub -1 /request std_msgs/String "どのようにヘルプフルですか?"
publishing and latching message for 3.0 seconds
robotuser@robotuser-PC:~$ rostopic pub -1 /request std_msgs/String "東京の明日の天気もわかりますか?"
publishing and latching message for 3.0 seconds
robotuser@robotuser-PC:~$ 

ターミナル 3 : チャットノードからの応答の ROS Topic を確認

3つ目のターミナルで ROS Topic /response に Chat Completion API からの応答がパブリッシュされているかを確認します.コンソール出力では文字コード化して可読性がないですが Python で print()rospy.loginfo() で出力すると ターミナル 1 のような読める日本語で表示されます.

robotuser@robotuser-PC:~$ rostopic echo /response 
data: "\u79C1\u306FAI\uFF08\u4EBA\u5DE5\u77E5\u80FD\uFF09\u30D9\u30FC\u30B9\u306E\u30D8\u30EB\
  \u30D7\u30D5\u30EB\u306A\u30A2\u30B7\u30B9\u30BF\u30F3\u30C8\u3067\u3059\u3002\u8CEA\
  \u554F\u3084\u554F\u984C\u89E3\u6C7A\u3001\u60C5\u5831\u306E\u63D0\u4F9B\u306A\u3069\
  \u3001\u304A\u624B\u4F1D\u3044\u3067\u304D\u308B\u3053\u3068\u304C\u3042\u308A\u307E\
  \u3057\u305F\u3089\u304A\u77E5\u3089\u305B\u304F\u3060\u3055\u3044\u3002"
---
data: "\u79C1\u306F\u591A\u304F\u306E\u60C5\u5831\u3068\u77E5\u8B58\u3092\u6301\u3064AI\u3067\
  \u3059\u306E\u3067\u3001\u69D8\u3005\u306A\u8CEA\u554F\u3084\u554F\u984C\u306B\u5BFE\
  \u3057\u3066\u304A\u7B54\u3048\u3059\u308B\u3053\u3068\u304C\u3067\u304D\u307E\u3059\
  \u3002\u4F8B\u3048\u3070\u3001\u65C5\u884C\u306E\u4E88\u7D04\u3084\u5929\u6C17\u4E88\
  \u5831\u306E\u78BA\u8A8D\u3001\u7FFB\u8A33\u3084\u7814\u7A76\u306E\u30B5\u30DD\u30FC\
  \u30C8\u3001\u4E00\u822C\u7684\u306A\u77E5\u8B58\u306E\u63D0\u4F9B\u306A\u3069\u3001\
  \u3055\u307E\u3056\u307E\u306A\u5206\u91CE\u3067\u304A\u624B\u4F1D\u3044\u3059\u308B\
  \u3053\u3068\u304C\u3067\u304D\u307E\u3059\u3002\u307E\u305F\u3001\u81EA\u7136\u8A00\
  \u8A9E\u51E6\u7406\u306E\u6280\u8853\u3092\u5229\u7528\u3057\u3066\u3001\u3042\u306A\
  \u305F\u306E\u8CEA\u554F\u3084\u8981\u6C42\u3092\u7406\u89E3\u3057\u3001\u6700\u9069\
  \u306A\u56DE\u7B54\u3084\u30BD\u30EA\u30E5\u30FC\u30B7\u30E7\u30F3\u3092\u63D0\u4F9B\
  \u3059\u308B\u3053\u3068\u3082\u53EF\u80FD\u3067\u3059\u3002\u304A\u56F0\u308A\u306E\
  \u3053\u3068\u3084\u7591\u554F\u304C\u3042\u308C\u3070\u3001\u3044\u3064\u3067\u3082\
  \u304A\u6C17\u8EFD\u306B\u304A\u77E5\u3089\u305B\u304F\u3060\u3055\u3044\u3002"
---
data: "\u3082\u3061\u308D\u3093\u3067\u3059\uFF01\u6771\u4EAC\u306E\u5929\u6C17\u4E88\u5831\
  \u3092\u8ABF\u3079\u3066\u307F\u307E\u3057\u3087\u3046\u3002\n\n\u79C1\u306F\u30EA\
  \u30A2\u30EB\u30BF\u30A4\u30E0\u306E\u30C7\u30FC\u30BF\u306B\u30A2\u30AF\u30BB\u30B9\
  \u3067\u304D\u308B\u308F\u3051\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u304C\u3001\
  \u4E00\u822C\u7684\u306B\u5929\u6C17\u4E88\u5831\u3092\u63D0\u4F9B\u3059\u308B\u516C\
  \u5F0F\u306E\u30A6\u30A7\u30D6\u30B5\u30A4\u30C8\u3084\u30A2\u30D7\u30EA\u3092\u5229\
  \u7528\u3057\u3066\u3001\u8A73\u7D30\u306A\u5929\u6C17\u4E88\u5831\u3092\u78BA\u8A8D\
  \u3059\u308B\u3053\u3068\u304C\u3067\u304D\u307E\u3059\u3002\u5929\u6C17\u4E88\u5831\
  \u306F\u983B\u7E41\u306B\u66F4\u65B0\u3055\u308C\u308B\u305F\u3081\u3001\u4E8B\u524D\
  \u306B\u78BA\u8A8D\u3059\u308B\u3053\u3068\u3092\u304A\u3059\u3059\u3081\u3057\u307E\
  \u3059\u3002\u4EE5\u4E0B\u306F\u4E00\u822C\u7684\u306A\u5929\u6C17\u4E88\u5831\u30B5\
  \u30FC\u30D3\u30B9\u306E\u5229\u7528\u65B9\u6CD5\u3067\u3059\u3002\n\n- \u30A4\u30F3\
  \u30BF\u30FC\u30CD\u30C3\u30C8\u691C\u7D22\u30A8\u30F3\u30B8\u30F3\u3067\u300C\u6771\
  \u4EAC\u306E\u5929\u6C17\u4E88\u5831\u300D\u3068\u691C\u7D22\u3059\u308B\u3068\u3001\
  \u73FE\u5728\u306E\u5929\u6C17\u60C5\u5831\u3068\u660E\u65E5\u306E\u5929\u6C17\u4E88\
  \u5831\u3092\u542B\u3080\u7D50\u679C\u304C\u8868\u793A\u3055\u308C\u307E\u3059\u3002\
  \n- \u30B9\u30DE\u30FC\u30C8\u30D5\u30A9\u30F3\u3084\u30BF\u30D6\u30EC\u30C3\u30C8\
  \u3092\u4F7F\u7528\u3057\u3066\u3044\u308B\u5834\u5408\u306F\u3001\u5929\u6C17\u4E88\
  \u5831\u3092\u63D0\u4F9B\u3059\u308B\u30A2\u30D7\u30EA\u3092\u30C0\u30A6\u30F3\u30ED\
  \u30FC\u30C9\u3057\u3066\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3059\u308B\u3053\u3068\
  \u3082\u3067\u304D\u307E\u3059\u3002\u5B9A\u756A\u306E\u30A2\u30D7\u30EA\u306B\u306F\
  \u300CWeather\u300D\u3084\u300CWeather Underground\u300D\u306A\u3069\u304C\u3042\
  \u308A\u307E\u3059\u3002\n\n\u3053\u308C\u3089\u306E\u65B9\u6CD5\u3092\u4F7F\u7528\
  \u3057\u3066\u3001\u660E\u65E5\u306E\u6771\u4EAC\u306E\u5929\u6C17\u3092\u78BA\u8A8D\
  \u3057\u3066\u307F\u3066\u304F\u3060\u3055\u3044\u3002\u5929\u6C17\u4E88\u5831\u306B\
  \u95A2\u3059\u308B\u8A73\u7D30\u306A\u60C5\u5831\u3092\u5165\u624B\u3059\u308B\u305F\
  \u3081\u306B\u306F\u3001\u5730\u5143\u306E\u6C17\u8C61\u5E81\u3084\u5929\u6C17\u4E88\
  \u5831\u30B5\u30FC\u30D3\u30B9\u306E\u516C\u5F0F\u30A6\u30A7\u30D6\u30B5\u30A4\u30C8\
  \u3092\u53C2\u7167\u3059\u308B\u3053\u3068\u3082\u304A\u3059\u3059\u3081\u3067\u3059\
  \u3002"
---

ChatGPT に対して問い合わせる側が人間であれば応答から自分で文脈をふまえて次の会話をすると思いますが,クライアントプログラムの場合は文脈をふまえた会話をしたければクライアント側のソフトウェアも ROS Topic を拾って自分で文脈を記録して解釈する必要があります.

ChatGPT vs ChatGPT

Chat Completion API との応答を ROS Topic を介してやり取りしているので,複数の Chat ノード(openai_chat_rostopic.py)を実行してトピックの remap をして互いのノードの応答を自らのノードの入力にすれば ChatGPT 同士で会話を続けるようにすることも簡単にできます.

そのために openai_chat.launch を次のように変更しました.

<launch>
  
  <arg name="key" default="$(env OPENAI_API_KEY)" />
  <arg name="model" default="gpt-3.5-turbo" />
  <arg name="service" default="false" />
  <arg name="opponent" default="false" />
  <arg name="prompt" default="You are a helpful assistant." />
  
  <node if="$(arg service)"
        pkg="openai_ros" type="openai_chat_server.py" name="openai_chat_service" output="screen">
    <param name="key" value="$(arg key)" />
    <param name="model" value="$(arg model)" />
  </node>
  
  <group unless="$(arg service)">
    <node pkg="openai_ros" type="openai_chat_rostopic.py" name="openai_chat_topic" output="screen">
      <param name="key" value="$(arg key)" />
      <param name="model" value="$(arg model)" />
      <param name="prompt" value="$(arg prompt)" />
    </node>
   
    <group ns="opponent" if="$(arg opponent)">
      <node pkg="openai_ros" type="openai_chat_rostopic.py" name="openai_chat_topic">
        <param name="key" value="$(arg key)" />
        <param name="model" value="$(arg model)" />
        <param name="prompt" value="You are a good talker." />
        <remap from="/opponent/request" to="/response" />
        <remap from="/opponent/response" to="/request" />
      </node>
    </group>  
  </group>
  
</launch>
  • L6: launch オプシション opponent で ChatGPT 同士の会話にするかを指定
  • L22: 2つ目のチャットノードは別のネームスペースとして区別
  • L23: 2つ目のチャットノードのコンソール出力も表示すると内容が重複するので output="screen" はなし
  • L26: prompt の設定でアシスタント同士だと会話が不自然な感じがしたので(とりあえず)2つ目のプロンプトは “You are a good talker.” としてみた
  • L27-28: remap/request/response を入れ替え

ターミナル 1 : 2つのチャットノードの起動

openai_chat.launch の起動オプション opponent:=true で2つのチャットノード実行とトピックの remap を行います.

起動した状態では応答は何もないですが ターミナル 2 から ROS トピック /request に最初のリクエストを1つパブリッシュすることで以後 ChatGPT 同士の会話が始まります.

robotuser@robotuser-PC:~$ roslaunch openai_ros openai_chat.launch opponent:=true
... logging to /home/robotuser/.ros/log/9cc9a1be-5dc8-11ee-9968-6b3ff6703622/roslaunch-robotuser-PC-35705.log
Checking log directory for disk usage. This may take a while.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://robotuser-PC:42615/

SUMMARY
========

PARAMETERS
 * /openai_chat_topic/key: sk-3JDluBbxsNuIhi...
 * /openai_chat_topic/model: gpt-3.5-turbo
 * /openai_chat_topic/prompt: You are a helpful...
 * /opponent/openai_chat_topic/key: sk-3JDluBbxsNuIhi...
 * /opponent/openai_chat_topic/model: gpt-3.5-turbo
 * /opponent/openai_chat_topic/prompt: You are a good ta...
 * /rosdistro: noetic
 * /rosversion: 1.16.0

NODES
  /
    openai_chat_topic (openai_ros/openai_chat_rostopic.py)
  /opponent/
    openai_chat_topic (openai_ros/openai_chat_rostopic.py)

auto-starting new master
process[master]: started with pid [35714]
ROS_MASTER_URI=http://localhost:11311

setting /run_id to 9cc9a1be-5dc8-11ee-9968-6b3ff6703622
process[rosout-1]: started with pid [35724]
started core service [/rosout]
process[openai_chat_topic-2]: started with pid [35731]
process[opponent/openai_chat_topic-3]: started with pid [35732]
[INFO] [1695882670.941923]: For 'system': You are a helpful assistant.
[INFO] [1695882677.451265]: request: サッカーの盛んな国を1つ挙げてください.
[INFO] [1695882679.335665]: assistant(token:60): ブラジルはサッカーの盛んな国として知られています.
[INFO] [1695882705.653403]: request: そうですね,ブラジルは世界でも有名なサッカーの強豪国として知られています.ブラジルではサッカーは国民的なスポーツであり,多くの人々が熱狂的に応援しています.

ブラジル代表チームは過去に5回のワールドカップ優勝を果たし,サッカーの歴史においても最も成功した国の一つです.有名な選手も多く輩出しており,ペレやジーコ,ロナウド,ロナウジーニョ,ネイマールなど,数々の伝説的なプレーヤーがブラジルから生まれています.

ブラジルではサッカーの試合が行われると,町中が一体となって応援に熱が入ります.カラフルな応援旗やドラム,歌声,そして華麗なサンバの踊りなど,独特のエネルギーと情熱が試合会場を包みます.

また,ブラジルには多くの有名なサッカークラブがあります.サンパウロのサンパウロFC,リオデジャネイロのフラメンゴ,サントス,リオグランデ・ド・スールのグレミオ,コリンチャンスなど,これらのクラブは強豪として名高いだけでなく,ファンの熱心さも有名です.

ブラジルのサッカーは単なるスポーツ以上のものであり,国民の誇りやアイデンティティの一部となっています.サッカーを通じて,ブラジルの文化や人々の情熱を感じることができるでしょう.
[INFO] [1695882713.741564]: assistant(token:754): その通りです.ブラジルのサッカーは国民の誇りであり,文化の一部として重要な役割を果たしています.多くの人々がサッカーに情熱を注ぎ,試合を熱狂的に応援する様子は見る価値があります.ブラジルのサッカーは世界中で愛され,その魅力は他の国にも広まっています.それだけに,ブラジルはサッカーの盛んな国として有名です.
[INFO] [1695882744.670979]: request: そうですね,ブラジルのサッカーの魅力は世界中に広まっており,多くの人々がその情熱に共感しています.ブラジル代表チームやクラブチームの試合は,テレビやインターネットを通じて世界中に配信されており,多くのサッカーファンがその魅力に触れることができます.

さらに,ブラジルのサッカー文化は技術,創造性,スピード,そしてリズム感を特徴としています.ブラジルのサッカー選手は驚くほど優れたテクニックを持ち,美しいプレーを見せることで知られています.彼らのキレのあるドリブル,正確なパス,そして豪快なシュートは,多くの人々に感動を与えます.

ブラジルのサッカーの成功は,その国の熱狂的なサッカーカルチャーとも関連しています.子供たちは幼い頃からサッカーボールを蹴り,街角やビーチでプレーする様子をよく見かけます.サッカースクールやアカデミーも充実しており,若い才能は早いうちから育成されています.

ブラジルのサッカーは単なるスポーツの一環ではなく,国民の誇りやアイデンティティの一部です.多くの人々が試合を通じて喜びや感動を共有し,サッカーを通じて結びついています.ブラジルのサッカー文化は他の国々にも影響を与え,彼らのスタイルやプレースタイルが憧れとなっています.

ブラジルのサッカーは確かに盛んな国であり,その魅力は世界中に広がっています.それはブラジルの人々の情熱と才能,そしてサッカー文化の豊かさによるものです.
[INFO] [1695882752.038028]: assistant(token:1519): 完全に同意します.ブラジルのサッカーカルチャーは,国民の情熱と才能,そして豊かなサッカー文化によって支えられています.その魅力は世界中に広まり,多くの人々がブラジルのサッカーに感動を覚えています.ブラジルのサッカーは間違いなく世界的な影響力を持っており,多くの国々で愛される存在です.
[INFO] [1695882760.235372]: request: ありがとうございます.ブラジルのサッカーは確かに世界的な影響力を持っており,多くの人々に愛されています.その独特のスタイルと情熱は,他の国々のサッカーカルチャーにも大きな影響を与えています.ブラジルのサッカーは常に進化し,新たな才能が次々に生まれることで,さらなる魅力と成功を築いていくでしょう.
^C[opponent/openai_chat_topic-3] killing on exit
[openai_chat_topic-2] killing on exit
[rosout-1] killing on exit
[master] killing on exit
shutting down processing monitor...
... shutting down processing monitor complete
done
robotuser@robotuser-PC:~$ 

ターミナル 2 : 最初の話題投下

robotuser@robotuser-PC:~$ rostopic pub -1 /request std_msgs/String "サッカーの盛んな国を1つ挙げてください."
publishing and latching message for 3.0 seconds
robotuser@robotuser-PC:~$ 

ROS ノードグラフ

rqt の ROS Node Graph でノードとトピックの様子を確認してみると,2つのノード /openai_chat_topic/opponent/openai_chat_topic とが互いの応答トピックを参照して循環していることが見て取れます.

ターミナル 1 の出力にも現れていますが段々と応答の文字数が互いに多くなる傾向があります.ChatGPT の token 数の上限に達して終わったりしますが,そうでない限りはずっと ChatGPT 同士で応答を続けるので終わらせたい場合は Ctrl-C で終わらせます.


文脈のデータを蓄積して多くなると Chat Completion API の token を消費してしまいますし,応答に時間がかかったりもします.一定時間会話がなかった場合やそれまでの文脈からがらりと話題を変える場合のために文脈を含めたメッセージデータを初期化するメソッドも運用上は必要かもしれません.

また,OpenAI 以外の大規模言語モデル(LLM)の API の ROS Topic ラッパがあれば(or を作れば)異なる LLM 間での会話も可能であろうと思います.


今回の記事はここまでです.

著者:yamamoto.yosuke

ChatGPT と ROS – ROS Service プログラムの文脈をふまえたチャット対応

本シリーズ前回の記事 ChatGPT と ROS – 文書生成 ROS ラッパー生成編(Chat Completion API) では OpenAI の Chat Completion API を利用した ROS Service ラッパ Python プログラムを Web の方の ChatGPT の助けを借りて作成した様子を紹介しました.

しかし,前回のプログラムは Chat Completion API を用いているものの使い方は「1問1答」形式で,それは Completion API を利用しているのと大きく変わらず,「文脈をふまえたチャット」形式ではありませんでした.

そこで前回から発展させて Chat Completion API を利用した「文脈をふまえたチャット」をする ROS ソフトウェアを次の2つの方法で実装してみました.

  1. ROS Service プログラムの文脈をふまえたチャット対応
    • 「1問1答」形式 →「チャット」形式
  2. ROS Topic を介した ChatGPT チャットプログラム
    • ROS Service の応答 → ROS Topic のやり取りによる ChatGPT とのチャット

今回の記事ではこれらのうち「1. ROS Service プログラムの文脈をふまえたチャット対応」を行った様子をお伝えします.

ROS Service プログラムの文脈をふまえたチャット対応

前回作成した Chat Completion API を利用しながらも「1問1答」形式だった ROS Service プログラムを「チャット」形式に対応させるて会話ができるように作り直します.

Chat Completion API を用いたチャット機能の実装については下記の Web 記事が ROS ではない Python プログラムですが大変参考になりました.

この記事によると Chat Completion API の応答を「チャット」の結果として機能させるためには「新しい問い」に一連の「チャットの全履歴」も含めて新たなメッセージとしたものを API に送ってリクエストする必要があります.

前回作成した ROS Service プログラムを発展させて,Chat Completion API を利用し履歴をふまえた ROS チャットソフトウェアを作成する場合,チャットの履歴を ROS Service のサーバ側で保持するか,サービスの呼び出し側で保持するか,システム的な最適解はさておき,プログラム的にはどちらの方法もあるかと思います.

今回作成した文脈をふまえたチャットに対応する ROS Service プログラムは Chat Completion API のシンプルな ROS ラッパーとしての位置づけは変えずにそのサービスを呼び出す ROS Node 側で履歴を保持して ROS Service にリクエストを送り Chat Completion API にアクセスする方法を採ってみました.

  • チャット対応 ROS Service プログラム概要
    • サービスサーバ側で Chat Completion API にアクセス(シンプルなラッパー)
    • サービスを呼び出す側でチャットの履歴を保持してチャットメッセージを作成
    • サービスのメッセージ型
      • JSON 形式の文字列として Chat Completion API へのメッセージおよびレスポンスのデータをやりとり

プログラム作成時の ChatGPT の活用

今回も Web サービスの ChatGPT の助けを借りてプログラムを作成したのですが,まるごと Python コードを作成するための問いの仕方が下手だったのか,ChatGPT の応答でしっくり来るようなソースコードは得られませんでした.そこで Python コードを記述する際の個別具体的な問題について ChatGPT の力を借りました.

例えば Chat Completion API へのメッセージやレスポンスを文字列(string)にしてやり取りする方法を聞きました.

これも聞いた時の少し前の時期に Web API を扱っていたことで Python API と Web API を混同していて,聞き方も JSON データ(文字列)と Python リストを混同していてかなりあやしいですが辻褄を合わせて何とか答えてくれるのも ChatGPT ですね.(小文字)

User

Chat Completion API Python における引数 messages に渡すのは json 形式の text 文字列であっていますか?


ChatGPT

Chat Completion APIのmessages引数に渡すものは,Pythonのリストとしてのデータ構造です.このリストは辞書のオブジェクトを要素として持ちます.通常,直接JSON形式のテキスト文字列を渡すのではなく,Pythonのデータ構造を使用します.

以下は,APIに渡すためのmessagesの例です:

messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "Who won the world series in 2020?"}
]

もし,このリストがJSON形式の文字列として提供されている場合,Pythonのjsonモジュールを使ってデコードすることで,上記のようなPythonのデータ構造に変換することができます:

import json

json_string = '[{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Who won the world series in 2020?"}]'
messages = json.loads(json_string)

OpenAIのAPIを直接使用する際や,HTTPリクエストを行う場合,リクエストのボディとしてJSON文字列を送信することがあります.その際には,上記のようにPythonのデータ構造をJSON文字列に変換(エンコード)したり,逆にJSON文字列からPythonのデータ構造に変換(デコード)したりする必要があります.しかし,多くのライブラリやSDKは,この変換を自動的に行ってくれるので,ユーザーが直接JSON文字列を扱う必要は少ないです.


User

json の python list を string に変換する場合はどうしますか?


ChatGPT

PythonのリストをJSON形式の文字列に変換するには,Pythonの標準ライブラリであるjsonモジュールのdumps関数を使用します.

以下に具体的な手順を示します.

1. jsonモジュールをインポートします.

import json

1. json.dumps関数を使用してPythonのリストをJSON形式の文字列に変換します.

data = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "Who won the world series in 2020?"}
]

json_string = json.dumps(data)
print(json_string)

この例では,dataという名前のPythonリストをJSON形式の文字列に変換し,それをjson_stringという変数に保存しています.

また,json.dumps関数にはいくつかのオプション引数があります.例えば,indentを指定することで,整形された(インデントされた)JSON文字列を取得することができます:

json_string_pretty = json.dumps(data, indent=4)
print(json_string_pretty)

これにより,出力は読みやすい形式で表示されます.


User

単純に str(data) とするのと異なりますか?


ChatGPT

str(data)json.dumps(data)は,多くの場合において似た結果を返すことがありますが,実際には異なる振る舞いを持ち,それぞれ異なる目的に最適化されています.以下にその主な違いをいくつか示します.

1. Unicode Handling:

json.dumps()は,Unicode文字列を適切にエンコードしてJSON互換のエスケープシーケンスにします.一方,str()はPythonのUnicode文字列の内部表現をそのまま返すことがあります.

data = {"key": "あいうえお"}
print(str(data))          # Python 2では "{'key': u'\u3042\u3044\u3046\u3048\u304a'}"
print(json.dumps(data))   # '{"key": "あいうえお"}'

2. Boolean Values:

json.dumps()は,PythonのTrueFalseをそれぞれtruefalseとしてJSONに変換します.str()は,Pythonの表現をそのまま返します.

data = {"key": True}
print(str(data))          # "{'key': True}"
print(json.dumps(data))   # '{"key": true}'

3. Single vs Double Quotes:

Pythonの辞書を文字列として表現するとき,str()はシングルクォート(‘)を使用します.しかし,JSON標準はプロパティ名や文字列値にダブルクォート(“)を使用するため,json.dumps()はダブルクォートを使用します.

data = {"key": "value"}
print(str(data))          # "{'key': 'value'}"
print(json.dumps(data))   # '{"key": "value"}'

これらの違いから,PythonのデータをJSONとして扱いたい場合や,他のシステムとの互換性を確保したい場合は,json.dumps()を使用するのが最適です.一方,デバッグ目的などでPythonの内部表現を文字列として見たいだけの場合は,str()を使用することができます.

また,Chat Completion API へ送るメッセージデータの中のroleの使い方についても ChatGPT に質問してみました.

User

Chat Completion API の渡し値の role における system と assistant の違いをおしえて?


ChatGPT

Chat Completion APIの文脈におけるroleの値systemassistantは,入力メッセージのコンテキストを形成するためのものです.各roleは以下のような意味を持ちます:

1. system:

  • systemの役割は,環境やセッションの全体的な指示やコンテキストを設定するためのものです.
  • このメッセージは,モデルに特定の動作やコンテキストを伝える指示として機能します.たとえば,ユーザーとの会話のスタイルやモードを指定する際に使用されることがあります.
  • 例: {"role": "system", "content": "You are an assistant that speaks like Shakespeare."}

2. assistant:

  • assistantの役割は,モデル自体の前の発言やレスポンスを示すためのものです.
  • これは,継続的な会話のコンテキストにおいて,モデルが以前にどのような回答や情報を提供したかを示すために使用されます.

各役割は,APIを通じてモデルとの対話を形成するためのコンテキストを提供する役割を果たします.たとえば,モデルにシェイクスピアのように話すように指示するsystemメッセージの後,ユーザーの質問に対するモデルの回答はシェイクスピアのスタイルで形成される可能性があります.

このように,roleの値を使用して,モデルとの対話のコンテキストや動作を微調整することができます.

ソースコード

文脈をふまえたチャット対応を行った ROS Service プログラムの作成や変更を行ったファイルは次のとおりです.

  • srv / ChatCompletion.srv
  • scripts / openai_chat_server.py
  • scripts / openai_chat_client.py
  • launch / openai_chat.launch

以下,それぞれのファイル内のコードを記載して少し説明をします.

srv / ChatCompletion.srv

先述したように Chat Completion API とやり取りする Python データ構造データを JSON 形式の文字列にして ROS Service との通信を行います.Chat Completion API に渡すデータをmessages,受取るデータをresponseというstring型にしています.

string messages
---
string response

scripts / openai_chat_server.py

ROS Service chat_service としてデータを JSON 形式の文字列として授受し,Chat Completion API へのアクセスを行っています.

#!/usr/bin/env python3

import rospy
import openai
import json

from openai_ros.srv import ChatCompletion, ChatCompletionResponse


def handle_chat_request(req):

    openai.api_key = rospy.get_param('~key')
    model = rospy.get_param('~model')
    
    res = ChatCompletionResponse()
    messages = json.loads(req.messages)
    
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages
    )
    
    return json.dumps(response)


def chat_service():
    rospy.init_node('chat_service')
    rospy.Service('chat_service', ChatCompletion, handle_chat_request)
    rospy.spin()


if __name__ == "__main__":
    chat_service()

scripts / openai_chat_client.py

ChatGPT に教えてもらったように role の「環境やセッションの全体的な指示やコンテキストを設定する」 systemprompt の内容を設定します.

ROS Node 内でユーザからのインプットを受け取り,これまでのメッセージ履歴に追加して,メッセージデータを JSON 文字列として ROS Service chat_service に送っています.サービスからの応答データは JSON 文字列から Python リストに変換されて含まれていた返答メッセージはprint()してメッセージ履歴にも追加することを繰り返しています.

#!/usr/bin/env python3

import sys, json
import rospy

from openai_ros.srv import ChatCompletion, ChatCompletionResponse


def chat_client(prompt="You are a helpful assistant."):
    
    messages = []
    messages.append({"role": "system", "content": str(prompt)})
    
    rospy.wait_for_service('chat_service')
    
    try:
        chat_service_client = rospy.ServiceProxy('chat_service', ChatCompletion)
    except rospy.ServiceException as e:
        print ("Service call failed: %s" % e)

    while not rospy.is_shutdown():
        
        # Get input from the user
        user_input = input("You: ")
        if user_input == "quit":
            exit()
        
        # Add user's input to the history
        messages.append({"role": "user", "content": user_input})
        
        msg_string = json.dumps(messages)
        
        # Call the service
        try:
            res_msg = chat_service_client(msg_string)
        except rospy.ServiceException as e:
            print ("Service call failed: %s" % e)
        
        response = json.loads(res_msg.response)
        
        content = response["choices"][0]["message"]["content"]
        role = response["choices"][0]["message"]["role"]
        token = response["usage"]["total_tokens"]
        
        print("\n%s(token:%d): %s\n" % (role, token, content))
        
        # Add GPT's response to the history
        messages.append({"role": str(role), "content": str(content)})


if __name__ == "__main__":
    if len(sys.argv) == 2:
        prompt = str(sys.argv[1])
    else:
        prompt = "You are a helpful assistant."
    
    print("Type \'quit\' to quit.\n")
    print("For \'system\': %s\n" % (prompt))
    rospy.init_node('chat_client')
    
    chat_client(prompt=prompt)

launch / openai_chat.launch

環境変数から OpenAI API Key を読み取って ROS Parameter に設定し, Chat Completion API にアクセスする ROS Service を実行します.

<launch>
  
  <arg name="key" default="$(env OPENAI_API_KEY)" />
  <arg name="model" default="gpt-3.5-turbo" />

  <node pkg="openai_ros" type="openai_chat_server.py" name="openai_chat" output="screen">
    <param name="key" value="$(arg key)" />
    <param name="model" value="$(arg model)" />
  </node>
  
</launch>

CMakeLists.txt

今回の ROS Service の定義ファイルを記述して使えるようにします.

add_service_files(
  FILES
  Completion.srv
  ChatCompletion.srv
)

generate_messages(

実行例

文脈をふまえたチャットへ対応させた ROS Service プログラムの実行例が次になります.

ターミナル1 : ROS Service サーバの起動

robotuser@robotuser-PC:~/openai_ws$ source ~/openai_ws/devel/setup.bash
robotuser@robotuser-PC:~/openai_ws$ export OPENAI_API_KEY="sk-..."
robotuser@robotuser-PC:~/openai_ws$ roslaunch openai_ros openai_chat.launch

ターミナル2 : ROS チャットノードの実行

robotuser@robotuser-PC:~/openai_ws$ source ~/openai_ws/devel/setup.bash
robotuser@robotuser-PC:~/openai_ws$ rosrun openai_ros openai_chat_client.py
Type 'quit' to quit.

For 'system': You are a helpful assistant.

You: hello

assistant(token:27): Hello! How can I assist you today?

You: ハロー

assistant(token:56): こんにちは!どのようにお手伝いできますか?

You: ボン・ニュイ

assistant(token:115): ボン・ニュイ!夜にちょっと早い挨拶ですね.何かお手伝いできることはありますか?

You: こんばんは をフランス語で言うと?

assistant(token:163): 「こんばんは」をフランス語で言うと「Bonsoir」となります.

You: bon nuit は日本語では?

assistant(token:224): 「bon nuit」はフランス語で「良い夜」を意味しますが,日本語では「おやすみなさい」と訳されます.

You: 間違えていました.ボンソワール

assistant(token:338): 間違いありません.「ボンソワール」はフランス語で「こんばんは」という意味です.おやすみなさいの言い方は「ボンヌィ」となります.ごめんなさい,混乱を招いてしまいました.どうかお許しください.

You: quit
robotuser@robotuser-PC:~/openai_ws$ 

このように今回は ROS Node 内でのチャット会話になっています.

前回ベースにしている https://github.com/davesarmoury/openai_ros が1問1答形式の Completion API にアクセスする ROS Service を提供していたことから Chat Completion API にアクセスする ROS ソフトウェアも ROS Service 形式で作成してみました.

比較的長文で1問1答を行う Completion API であれば ROS Service や ROS Action (actionlib) のソフトウェアにするのも一理あるように思えます.しかし短文のチャットを扱う Chat Completion API へのアクセスであれば ROS Topic を介したメッセージのやり取りの方が ROS ノード内のチャット会話に限られず,より ROS 親和的かつよりシンプルな構成になるのでは?と思いました.

本シリーズ次回の記事では冒頭で述べました文脈をふまえたチャットを実現する2つの方法のもう一方の「ChatGPT と ROS – ROS Topic を介した ChatGPT チャットプログラム」を作成した様子を紹介する予定です.

今回の記事はここまでです.

著者:yamamoto.yosuke

ChatGPT と ROS – 文書生成 ROS ラッパー生成編(Chat Completion API)

本シリーズ前回の記事 ChatGPT と ROS – 文書生成 ROS ラッパー利用編(Completion API) では OpenAI API の ROS ラッパーの中で Completion API を利用している ROS1 の Python ラッパ https://github.com/davesarmoury/openai_ros を使ってみました.

今回は OpenAI の Chat Completion API を利用した ROS ラッパ Python プログラムを Web サービスの方の ChatGPT の助けをかりながら作成した様子を紹介します.

OpenAI の文書生成 API 概要

本シリーズ第1回の記事 ChatGPT と ROS – 調査編 でもふれましたが OpenAI の文書生成 API について簡単にまとめますと,「1問1答形式」の Completion API と,「対話した文脈を含むチャット対話形式」の Chat Completion API の2つがあります.

  • OpenAI の文書生成 API
    • Completion API : 1問1答形式
    • Chat Completion API : 文脈を含むチャット対話形式

前回の記事ではこの2つのうち「1問1答」形式の Completion API を利用ました.今回の記事ではもう一方の「チャット対話」形式のインタフェースである Chat Completion API を ROS から利用してみます.

開発・実行環境

今回は Web サービスの ChatGPT に Chat Completion API を使ったプログラムを生成してもらいながら進めましたのでそれも含めて開発・実行環境の構成は次のようになっています.

  • Ubuntu 20.04
  • ROS Noetic
  • OpenAI ChatGPT のアカウントを持っている(今回筆者は ChatGPT-4 を利用)
  • OpenAI API の利用が有効なアカウントを持っている
    • API Key を取得済

ChatGPT でのプログラム生成

まずは ChatGPT の Web サービスに ChatGPT の Python API にアクセスするための Python プログラムを書いてもらいました.

User

ChatGPT の Python API を用いたアクセスプログラムを書いてください.


ChatGPT

まず,OpenAIのPythonクライアントライブラリをインストールする必要があります.次のコマンドでインストールできます.

pip install openai

そして,以下はOpenAIのGPTを使用するための基本的なPythonコードです.この例では,プロンプトとして”Translate the following English text to French: ‘{}'”を使用し,その'{}’の部分に翻訳したい英文を挿入しています.

import openai

openai.api_key = 'your-api-key'

response = openai.ChatCompletion.create(
  model="gpt-4.0-turbo",
  messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Translate the following English text to French: '{}'"}
    ]
)

print(response['choices'][0]['message']['content'])

上記のコードは,APIキーを設定し,特定のモデル(この場合は”gpt-4.0-turbo”)を指定して,ChatCompletion APIを呼び出す基本的な構造を示しています.messagesパラメータは会話の履歴を表し,最初のメッセージは通常,モデルに役割(この場合は”helpful assistant”)を設定するためのシステムメッセージです.ユーザーメッセージはその後に続きます.

注意: ‘your-api-key’の部分は,OpenAIから提供される実際のAPIキーに置き換える必要があります.また,APIの使用は料金が発生しますので,公式ドキュメンテーションを確認して使用制限とコストを理解することが重要です.

なお,GPT-4のリリース後もAPIの基本的な使い方はこれまでと同様ですが,詳細な情報や更新内容についてはOpenAIの公式ドキュメンテーションをご覧ください.

次にこの ChatGPT にアクセスする Python コードを ROS の Service に対応させたプログラムを作成してもらいました.

User

この Python プログラムから ROS の service プログラムラッパーを作成してください.


ChatGPT

ROS (Robot Operating System)は,ロボットのためのフレームワークで,様々なハードウェアやソフトウェアの要素を組み合わせて使うことができます.ここでは,PythonのOpenAI GPT-4クライアントをROSサービスとしてラップする基本的な例を示します.

まず,適切なメッセージタイプを使用するためのsrvファイルを作成します.この例では,リクエストとレスポンスが共に一つの文字列を持つシンプルなサービスを定義します.

# GptService.srv
string input
---
string output

次に,このサービスを実装するPythonのROSノードを作成します.このノードは,指定された文字列を翻訳するサービスを提供します.

#!/usr/bin/env python
import rospy
from openai_ros.srv import GptService, GptServiceResponse
import openai

def handle_gpt_request(req):
    openai.api_key = 'your-api-key'
    
    response = openai.ChatCompletion.create(
        model="gpt-4.0-turbo",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "Translate the following English text to French: '{}'".format(req.input)}
        ]
    )

    return GptServiceResponse(response['choices'][0]['message']['content'])

def gpt_service():
    rospy.init_node('gpt_service')
    s = rospy.Service('gpt_service', GptService, handle_gpt_request)
    print("Ready to translate English text to French.")
    rospy.spin()

if __name__ == "__main__":
    gpt_service()

このPythonスクリプトは,ROSノードgpt_serviceを作成し,それがGptServiceサービスを提供するようにします.サービスリクエストが来ると,handle_gpt_request関数が呼び出され,その中でOpenAIのGPT-4クライアントが使われてリクエストされたテキストを翻訳します.

このスクリプトをROSノードとして実行するためには,そのファイルに実行可能なパーミッションを与える必要があります.以下のコマンドで実行可能なパーミッションを与えることができます:

chmod +x gpt_service.py

また,OpenAIのAPIキーを設定する部分では,実際のキーに’your-api-key’を置き換える必要があります.そして,APIの使用は料金が発生しますので,公式ドキュメンテーションを確認して使用制限とコストを理解することが重要です.

なお,上記のコードはあくまで一例であり,具体的な使用シーンや要件によりますので,適宜修正や拡張を行ってください.

ChatGPT に最初に生成してもらった Python プログラムにおいてプロンプトの内容が固定されているため,それを基に生成した ROS ラッパープログラムも固定プロンプトへの応答結果を出すだけになってしまいました.

そこでプロンプトを変更可能なようにプログラムを変更してもらいました.

User

ChatGPT に問うプロンプトを変えられるようにプログラムを変更してください.


ChatGPT

もちろんです.以下に,GPTサービスリクエストにプロンプトを含めるように改変したROSサービスの例を示します.

まず,新たなsrvファイルを作成します.この例では,リクエストにプロンプトとインプットの2つの文字列を持つサービスを定義します.

# GptService.srv
string prompt
string input
---
string output

次に,このサービスを実装するPythonのROSノードを作成します.

#!/usr/bin/env python
import rospy
from openai_ros.srv import GptService, GptServiceResponse
import openai

def handle_gpt_request(req):
    openai.api_key = 'your-api-key'
    
    response = openai.ChatCompletion.create(
        model="gpt-4.0-turbo",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "{} '{}'".format(req.prompt, req.input)}
        ]
    )

    return GptServiceResponse(response['choices'][0]['message']['content'])

def gpt_service():
    rospy.init_node('gpt_service')
    s = rospy.Service('gpt_service', GptService, handle_gpt_request)
    print("Ready to handle GPT-4 requests.")
    rospy.spin()

if __name__ == "__main__":
    gpt_service()

このPythonスクリプトは,ROSノードgpt_serviceを作成し,それがGptServiceサービスを提供するようにします.サービスリクエストが来ると,handle_gpt_request関数が呼び出され,その中でOpenAIのGPT-4クライアントが使われてリクエストされたプロンプトとテキストを処理します.

上記のコードを実行する前に,Pythonスクリプトに実行可能なパーミッションを与えることを忘れないでください.

ここまでの ChatGPT の応答で ROS の Python プログラムとして動きそうな感じのソースコードが生成されたように思いましたので実際に実行しながら修正してみることにしました.

ChatGPT が生成したプログラムの修正

今回は ChatGPT と ROS – 文書生成 ROS ラッパー利用編(Completion API) で利用した ROS パッケージ https://github.com/davesarmoury/openai_ros をベースに Chat Completion API を利用する機能を付け加えるかたちで進めました.

ChatGPT が生成した Chat Completion API を利用する ROS Python プログラムを使って Chat Completion API を利用できるよう ROS パッケージに変更を加えた箇所をまとめると次のようになります.

  • openai_chat_node.py の追加
    • ChatGPT が生成したコードからの修正点
      • #!/usr/bin/env python
        • → Python3 に #!/usr/bin/env python3
      • openai.api_key = 'your-api-key'
        • → ROS パラメータから取得する方法に変更
          openai.api_key = rospy.get_param('~key')
      • model="gpt-4.0-turbo",
        • → GPT 3.5 に変更 model="gpt-3.5-turbo",
      • print("Ready to handle GPT-4 requests.")
        • → GPT 3.5 に変更 print("Ready to handle GPT-3.5 requests.")
  • GptService.srv の追加
    • ChatGPT が生成したコードをそのまま利用
  • CMakeLists.txs 内に GptService.srv の記述追加
  • openai_chat.launch の追加
    • 主に OpenAI API Key を ROS パラメータとするために作成

openai_chat_node.py

#!/usr/bin/env python3
import rospy
from openai_ros.srv import GptService, GptServiceResponse
import openai

def handle_gpt_request(req):
#    openai.api_key = 'your-api-key'
    openai.api_key = rospy.get_param('~key')
    
    response = openai.ChatCompletion.create(
#        model="gpt-4.0-turbo",
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "{} '{}'".format(req.prompt, req.input)}
        ]
    )

    return GptServiceResponse(response['choices'][0]['message']['content'])

def gpt_service():
    rospy.init_node('gpt_service')
    s = rospy.Service('gpt_service', GptService, handle_gpt_request)
#    print("Ready to handle GPT-4 requests.")
    print("Ready to handle GPT-3.5 requests.")
    rospy.spin()

if __name__ == "__main__":
    gpt_service()

プログラムの本筋の部分は ChatGPT が生成したコードから修正の必要はありませんでした.

GptService.srv

# GptService.srv
string prompt
string input
---
string output

CMakeLists.txt

add_service_files(
  FILES
  Completion.srv
  GptService.srv
)

GptService.srv のサービスが利用できるように CMakeLists.txt に加筆しました.このあたりの修正箇所の洗い出しも ChatGPT に問うてみるのも修正規模が大きい場合にはありかもしれません.

openai_chat.launch

<launch>
  <arg name="key" default="$(env OPENAI_API_KEY)" />
  <arg name="max_tokens" default="256" />
  <arg name="model" default="gpt-4.0-turbo" />

  <node pkg="openai_ros" type="openai_chat_node.py" name="openai_chat" output="screen">
    <param name="key" value="$(arg key)" />
    <param name="max_tokens" value="$(arg max_tokens)" />
    <param name="model" value="$(arg model)" />
  </node>  
</launch>

モデルを GPT-3.5 と GPT-4 で launch オプションで切り替えて使おうかと思っていたのですが,Web と API への課金は別らしく今回は API では GPT-3.5 のみ利用可能な状況でしたので openai_chat_node.py にモデル名を直書きしたまま使ってしまいました.

修正したプログラム実行の様子

roslaunch openai_ros openai_chat.launch を起動してからもう1つのターミナルで ROS サービスで rosservice call /gpt_service '{prompt: "(プロンプト)", input: "(内容)"}'
output: "Mon nom est Robotuser."
のように利用します.

ターミナル1

robotuser@robotuser-PC:~/openai_ws$ source ~/openai_ws/devel/setup.bash
robotuser@robotuser-PC:~/openai_ws$ roslaunch openai_ros openai_chat.launch 
... logging to /home/robotuser/.ros/log/9d61ced2-f54d-11ed-b5ca-c10df8d90fa9/roslaunch-robotuser-PC-41157.log
Checking log directory for disk usage. This may take a while.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://robotuser-PC:35595/

SUMMARY
========

PARAMETERS
 * /openai_chat/key: sk-3JDluBbxsNuIhi...
 * /openai_chat/max_tokens: 256
 * /openai_chat/model: gpt-4.0-turbo
 * /rosdistro: noetic
 * /rosversion: 1.16.0

NODES
  /
    openai_chat (openai_ros/openai_chat_node.py)

auto-starting new master
process[master]: started with pid [41165]
ROS_MASTER_URI=http://localhost:11311

setting /run_id to 9d61ced2-f54d-11ed-b5ca-c10df8d90fa9
process[rosout-1]: started with pid [41175]
started core service [/rosout]
process[openai_chat-2]: started with pid [41182]
Ready to handle GPT-3.5 requests.

ターミナル2

robotuser@robotuser-PC:~/openai_ws$ source ~/openai_ws/devel/setup.bash
robotuser@robotuser-PC:~/openai_ws$ rosservice call /gpt_service '{prompt: "Translate following to French:", input: "My name is Robotuser."}'
output: "Mon nom est Robotuser."
robotuser@robotuser-PC:~/openai_ws$ rosservice call /gpt_service '{prompt: "Translate following to Spanish:", input: "My name is Robotuser."}'
output: "Mi nombre es Robotuser."
robotuser@robotuser-PC:~/openai_ws$ rosservice call /gpt_service '{prompt: "Translate following to Japanese:", input: "My name is Robotuser."}'
output: "\u79C1\u306E\u540D\u524D\u306F\u30ED\u30DC\u30C3\u30C8\u30E6\u30FC\u30B6\u30FC\u3067\
  \u3059\u3002"
robotuser@robotuser-PC:~/openai_ws$ 
  • 注)コマンド全体は横スクロールにて表示してください.

このサービス利用例では “My name is Robotuser.” をフランス語,スペイン語,日本語に翻訳するよう各プロンプトを送りました.
日本語への翻訳指示した output が文字コード化していたので Unicode 変換すると次のようになりました.

私の名前はロボットユーザーです.

このように ChatGPT の Web サービスを利用してコードを生成してもらい,OpenAI の Chat Completion API を ROS から利用できるようになりました.

しかし今回のプログラムは Chat Completion API を用いているものの「1問1答」形式の使い方をしていて,それは Completion API を利用している場合と大きく変わらず, “文脈” をふまえた「チャット」形式ではありませんでした.

本シリーズ次回の記事では今回の Chat Completion API を利用する ROS サービスプログラムを文脈をふまえた「チャット」をする ROS プログラムに改造した様子をお伝えする予定です.

著者:yamamoto.yosuke

ChatGPT と ROS – 文書生成 ROS ラッパー利用編(Completion API)

本シリーズ前回の記事 ChatGPT と ROS – 調査編 では ChatGPT の ROS を介した利用について少し調べてみたことをお伝えしました.

今回は OpenAI API の ROS ラッパーの中で Completion API を利用している ROS1 の Python ラッパ https://github.com/davesarmoury/openai_ros を使ってみた様子を紹介します.

実行環境

今回は次の環境で OpenAI API の ROS を介した実行を行っています.

  • Ubuntu 20.04
  • ROS Noetic
  • OpenAI API の利用が有効なアカウントを持っている
    • API Key を取得済

OpenAI API は新規登録後 3ヶ月 の期限がありますが 5ドル分 の無料クレジットが付与されるのでお試し利用することができます.(2023年8月中旬時点)

API Key の取得は OpenAI API の Web ページでログインした状態で下記リンク先の API keys のページから取得します.

インストールとビルド

実行環境の準備が整いましたらインストールとビルドを行います.

ROS のインストール

ROS は既にインストールされているようでしたら改めてインストールする必要はありません.

加えて下記の catkin ツール関係もインストールしておきます.

$ sudo apt install python3-osrf-pycommon python3-catkin-tools

OpenAI Python ライブラリのインストール

OpenAI の Python ライブラリが必要ですので pip からインストールします.

$ sudo apt install python3-pip
$ pip install --upgrade openai

ワークスペースの作成とビルド

今回は openai_ws という名前のワークスペースを作成してソースコードのクローンとビルドを行っています.

$ mkdir -p ~/openai_ws/src
$ cd ~/openai_ws/src/
$ git clone https://github.com/davesarmoury/openai_ros.git
$ cd ~/openai_ws/
$ rosdep install -y -r --from-paths src --ignore-src
$ catkin build
$ source ~/openai_ws/devel/setup.bash

openai_ros の実行

ワークスペースでビルドした openai_ros の ROS プロセスを実行します.

まず1つ目のターミナルで API Key を環境変数 OPENAI_API_KEY として export で設定しておきます.$ export OPENAI_API_KEY="sk-..."sk-... の部分は各自の OpenAI API アカウントで作成した API Key の内容に置き換えてください.

OpenAI の Completion API を利用するための ROS サービスサーバを実行するために openai.launch を起動します.

ターミナル 1

$ source ~/openai_ws/devel/setup.bash
$ export OPENAI_API_KEY="sk-..."
$ roslaunch openai_ros openai.launch max_tokens:=256

2つ目のターミナルから1つ目のターミナルで実行している OpenAI Completion API の ROS サービスにプロンプトを “Write a poem about OpenAI” としてサービスコールを行います.

ターミナル 2

$ source ~/openai_ws/devel/setup.bash
$ rosservice call /get_response '{prompt: "Write a poem about OpenAI"}'
finish_reason: "stop"
text: "\n\nOpenAI, a force of nature,\nA tool of the future,\nA way to explore the unknown,\n\
  A way to make the world better.\n\nA way to make machines smarter,\nA way to make\
  \ them think,\nA way to make them learn,\nA way to make them act.\n\nA way to make\
  \ them understand,\nA way to make them act,\nA way to make them do,\nA way to make\
  \ them react.\n\nOpenAI, a force of nature,\nA tool of the future,\nA way to explore\
  \ the unknown,\nA way to make the world better."
model: "text-davinci-003"
completion_tokens: 134
prompt_tokens: 6
total_tokens: 140

Completion API から ROS サービス経由で応答が帰ってきました.text: に応答内容があります.

text 部分の改行コードなどを除くと次のようになっています.

OpenAI, a force of nature,
A tool of the future,
A way to explore the unknown,
A way to make the world better.

A way to make machines smarter,
A way to make them think,
A way to make them learn,
A way to make them act.

A way to make them understand,
A way to make them act,
A way to make them do,
A way to make them react.

OpenAI, a force of nature,
A tool of the future,
A way to explore the unknown,
A way to make the world better.

…だそうです.

プロンプトを日本語で例えば '{prompt: "OpenAI についての40字以 内のポエムを書いてください"}' 記述しても応答はありますが rosservice のコールの応答をそのままコンソール出力した状態ですと text: 内は文字コード化されていて可読性がありませんでした.

注)コマンド全文は枠内を横スクロールして表示してください.

$ rosservice call /get_response '{prompt: "OpenAI についての40字以 内のポエムを書いてください"}'
finish_reason: "stop"
text: "\n\nOpenAI\u306F\u3001\u4EBA\u985E\u306E\u672A\u6765\u3092\u5B88\u308B\u305F\u3081\
  \u306B\u3001AI\u3092\u4F7F\u3063\u3066\u6280\u8853\u3092\u767A\u5C55\u3055\u305B\
  \u308B\u3002\u79C1\u305F\u3061\u306F\u3001AI\u3092\u4F7F\u3063\u3066\u3001\u3088\
  \u308A\u826F\u3044\u672A\u6765\u3092\u5275\u9020\u3057\u3088\u3046\u3002"
model: "text-davinci-003"
completion_tokens: 81
prompt_tokens: 31
total_tokens: 112
$

文字コード表示 解決法1 – ascii2uni を使う

文字コード表示を文字コード変換の ascii2uni で解決してみます.ascii2uni を使うため uni2ascii をインストールします.

$ sudo apt install uni2ascii

ターミナル 2

ROS サービスコールの結果に対して | ascii2uni -a U -q をパイプしてコード変換を行います.

注)コマンド全文は枠内を横スクロールして表示してください.

$ rosservice call /get_response '{prompt: "OpenAI についての40字以 内のポエムを書いてください"}' | ascii2uni -a U -q
finish_reason: "stop"
text: "\n\nOpenAIは,人類の未来を守るため\
  に𰀚Iを使って技術を発展させ\
  る.私たちは𰀚Iを使って,よ\
  り良い未来を創造しよう."
model: "text-davinci-003"
completion_tokens: 81
prompt_tokens: 31
total_tokens: 112
$ 

一部文字化けしてしまっているようです.おそらく \u3001(=読点「,」) + AI\u3001A + I と判断して違う文字を表示しようとしているようです.「文字コードの”読点”」+「平文英数字」の組み合わせ以外は大体 ascii2uni で表示できそうです.

文字コード表示 解決法2 – Python を使う

文字コード化されたものは Python の print() 内で解決されて可読性のある日本語の状態で出力されますので,今回の openai_ros の ROS サービスを Python からコールするプログラム openni_get_completion.py を書きました.

openni_get_completion.py

#!/usr/bin/env python3

import sys
import rospy

from openai_ros.srv import Completion, CompletionResponse


def get_response_client(prompt):
  request = '{prompt: ' + str(prompt) +'}'
  rospy.wait_for_service('get_response')
  try:
    get_response = rospy.ServiceProxy('get_response', Completion)
    response = get_response(request, 0)
    return response
  except rospy.ServiceException as e:
    print ("Service call failed: %s"%e)


if __name__ == "__main__":
  if len(sys.argv) == 2:
    prompt = str(sys.argv[1])
  else:
    prompt = "Write a poem about OpenAI"
  
  print("Prompt: %s\n" % (prompt))
  
  response = get_response_client(prompt)
  
  print("Response: \n%s\n" % (response))
  print("Text: %s\n" % (response.text))

先程の ターミナル1 で openai.launch を実行している状態で ターミナル2 から openni_get_completion.py を実行します.

ターミナル 2

$ source ~/openai_ws/devel/setup.bash
$ rosrun openai_ros openai_get_completion.py 
Prompt: Write a poem about OpenAI

Response: 
finish_reason: "stop"
text: "\n\nOpenAI, a force of nature,\nA powerful tool of creation,\nAble to learn and adapt,\n\
  Able to think and create.\n\nA tool of the future,\nA tool of the present,\nA tool\
  \ of the past,\nA tool of the ages.\n\nA tool of the people,\nA tool of the world,\n\
  A tool of the universe,\nA tool of the gods.\n\nOpenAI, a force of nature,\nA powerful\
  \ tool of creation,\nAble to learn and adapt,\nAble to think and create."
model: "text-davinci-003"
completion_tokens: 124
prompt_tokens: 11
total_tokens: 135

Text: 

OpenAI, a force of nature,
A powerful tool of creation,
Able to learn and adapt,
Able to think and create.

A tool of the future,
A tool of the present,
A tool of the past,
A tool of the ages.

A tool of the people,
A tool of the world,
A tool of the universe,
A tool of the gods.

OpenAI, a force of nature,
A powerful tool of creation,
Able to learn and adapt,
Able to think and create.

$ 

実行時にプロンプトの引数を渡していないのでプログラム内に書かれてるデフォルトのプロンプト “Write a poem about OpenAI” に対する英語のポエムが返ってきています.英語でも Python の print() で出力すると改行コードが見えなくなるので読みやすくなっています.

次は引数として日本語のプロンプト “OpenAI についての40字以内のポエムを書いてください.” を渡して openni_get_completion.py を実行します.

ターミナル 2

$ rosrun openai_ros openai_get_completion.py "OpenAI についての40字以内のポエムを書いてください."
Prompt: OpenAI についての40字以内のポエムを書いてください.

Response: 
finish_reason: "stop"
text: "\n\nOpenAI\u306F\u672A\u6765\u3092\u7167\u3089\u3059\u5149\u3001\u672A\u6765\u3092\
  \u5207\u308A\u958B\u304F\u92ED\u3044\u5203\u3002\u79C1\u305F\u3061\u306E\u672A\u6765\
  \u3092\u8C4A\u304B\u306B\u3057\u3066\u304F\u308C\u308B\u3002"
model: "text-davinci-003"
completion_tokens: 58
prompt_tokens: 39
total_tokens: 97

Text: 

OpenAIは未来を照らす光,未来を切り開く鋭い刃.私たちの未来を豊かにしてくれる.

$ 

日本語でも可読性のある整形された状態で読めました.そうなんですね…

ポエムはさておき,openai_ros パッケージを利用して OpenAI の Completion API を ROS から英語と日本語のどちらでも利用することができました.

今回の記事はここまでです.


本シリーズ次回の記事は OpenAI の Chat Completion API を ROS から利用する Python プログラムを OpenAI の ChatGPT に教えてもらいながら作成した様子を紹介する予定です.

著者:yamamoto.yosuke

ChatGPT と ROS – 調査編

文書生成などで何かと話題の ChatGPT の ROS を介した利用について少し調べてみました.

本記事の筆者は AI に興味はあるものの利用したことがほとんどない立場ですが,ChatGPT などについて調べてみると AI 初心者でも OpenAI の API と ROS を介して文章を生成することができました.加えて ChatGPT の文章生成機能の力を借りてその API を使った ROS ラッパープログラムも作ることができました.その様子を次のようなトピックで複数回に分けて記事にします.

  1. 調査編(本記事)
  2. 文書生成 ROS ラッパー利用編(Completion API)
  3. 文書生成 ROS ラッパー生成編(Chat Completion API)

OpenAI/ChatGPT の API

ChatGPT を提供しているのは OpenAI 社ですので “OpenAI” や “ChatGPT” でネット検索してみると OpenAI Gym の利用だったり,ChatGPT だったりといろいろとあるようでした.全く体系的・網羅的ではないと思うのですが,今回調査してみて得られた情報を大まかに分類してみると次のような構成になりました.

  • 強化学習を行うための OpenAI Gym
  • 文書生成系の OpenAI API も複数
    • Chat Completion API
    • Completion API

まず OpenAI Gym というのは強化学習を行うためのものでロボットの動作経路の学習などに用いられていて,今回の目的としていた文書生成とは異なる機能を提供しているようです.文書生成 API も Completion API と Chat Completion API と複数に分かれていました.

Web ページから ChatGPT-4 に Completion と Chat Completion の API の違いを聞いてみました.

OpenAI API の Completion API と Chat Completion API の違いを教えてください.

Completion APIとChat Completion APIは,OpenAIのGPT(Generative Pretrained Transformer)をベースとしたテキスト生成APIです.しかし,それぞれの使用方法や特性は少し異なります.

Completion API:

  • Completion APIは,提供されたプロンプトに対する続きのテキストを生成します.つまり,モデルに入力を1つ与え,それに対する出力を1つ得ます.
  • このAPIは,多くの一般的なテキスト生成タスクに使用できます.例えば,文章の完成,質問への答え,電子メールのドラフト作成などです.

Chat Completion API:

  • Chat Completion APIは,会話形式のテキスト生成に最適化されています.複数のメッセージを入力として受け取り,それに基づいて応答を生成します.
  • それぞれのメッセージには,「role」(’system’,’user’,’assistant’)と「content」があります.’system’ メッセージは,チャット全体のコンテキストを設定します.’user’ メッセージはモデルに指示を出し,’assistant’ メッセージはモデルの先行の応答を提供します.
  • このAPIは,対話型のタスクやマルチターンの会話に特に適しています.

使用するAPIは,あなたがどのようなタスクを実行したいか,またはどのような出力を期待しているかによって異なります.

Chat Completion は名前の「チャット」のとおり文脈を含む対話形式で,Completion は1問1答形式のようです.

Completion と Chat Completion の違いについて対比的に解説されている記事もあり参考になりました.

OpenAI/ChatGPT と ROS

ROS では OpenAI/ChatGPT がどのように使われているのか?ということで調べてみると,まず ROS Wiki にある openai_ros は強化学習の OpenAI Gym の機能を利用するパッケージのようでした.

また GitHub 内で “openai” と “ros” を組み合わせて検索してみると https://github.com/search?q=openai+ros&type=repositories&p=1 かなりヒットします.そのうち文書生成に関するリポジトリをピックアップしてリストにします.

これらの既存の OpenAI API の ROS ラッパーの中で Completion API を利用している ROS1 の Python ラッパ https://github.com/davesarmoury/openai_ros を使ってみた様子を次回の OpenAI と ROS の記事でお伝えする予定です.

今回の記事はここまでです.

著者:yamamoto.yosuke

信州大学の HIRO ロボットソフトウェアが Ubuntu 20.04 + ROS Noetic に対応

先日,長野市にある信州大学の山崎研究室を訪問して Ubuntu 20.04 および ROS Noetic に対応した HIRO ロボットソフトウェアを納品しました.

山崎研究室では HIRO で AI を用いたロボット制御などを行っているとのことで,今回は GPU ボードを搭載したワークステーションに Ubuntu 20.04 および ROS Noetic に対応した HIRO ロボットソフトウェアをインストールしました.

HIRO ロボットは新しいソフトウェアを得て今後も活躍してくれることと思います.


なお, 今回の HIRO とともに TORK では NEXTAGE OPEN も Ubuntu 20.04 および ROS Noetic に対応したロボットソフトウェアの動作確認をしました.

NEXTAGE OPEN や HIRO を Python3 で動かすことや ROS Noetic で使うことにご興味がありましたら,TORK( info@opensource-robotics.tokyo.jp )にお問い合わせいただけたらと思います.


関連記事: 信州大学 山崎研究室でHiroに会いました!

信州大学 山崎研究室でHiroに会いました!

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(14)MoveIt の動モデルの作成

本シリーズ前回の記事 Gazebo/MoveIt のための 3D モデリング(13)MoveIt の静モデルの作成 では CAD などからエクスポートしたメッシュデータファイルを MoveIt の静モデルとしてモデルファイルに組み込んで表示する方法を紹介しました.

今回は洗濯機の URDF (Unified Robot Description Format) モデルにドアのヒンジなどの動く箇所を設定して,より機械らしい(ロボットに近い)モデルにする様子を紹介します.

各リンクモデルメッシュのエクスポート

前回の MoveIt の静モデル作成においては洗濯機全体として1つのメッシュデータファイル( DAE もしくは STL )をエクスポートして利用しました.

MoveIt のリンクモデル作成では各リンクに対応したメッシュをそれぞれエクスポートしてそれぞれのリンクのメッシュファイルとして利用します.

今回の洗濯機モデルでは次の3つのリンク構成にします.

  • 洗濯機本体: main-body
  • 洗濯槽の扉: door
  • 洗剤投入トレイ: tray

今回は「洗濯機本体(main-body)」は元の洗濯機全体の座標系そのままとするので配置の変更はしません.

「洗濯槽の扉(door)」と「洗剤投入トレイ(tray)」の形状データを各リンクの座標系原点に配置します.

元々の洗濯機全体の座標系で配置されたオブジェクトを残しつつ,別途各リンクのエクスポート用にリンク座標系の原点にオブジェクトを配置してメッシュデータとしてエクスポートします.

Rhinoceros では右の図のようにオブジェクトを含む既存のレイヤを右クリックするとメニューに「レイヤとオブジェクトを複製」ができるのでこの機能で複製した先のレイヤで作業すると良いでしょう.

各リンク座標系基準の配置用レイヤでそれぞれの各リンクは次のように配置しました.

  • 洗濯機本体: main-body
    • 位置: 変更なし
    • 角度: 変更なし
  • 洗濯槽の扉: door
    • 位置: 開閉ヒンジ回転軸の中心が座標原点
    • 角度: 開閉ヒンジ回転軸を Z軸 に一致
  • 洗剤投入トレイ: tray
    • 位置: 最後下部エッジ中心が座標原点
    • 角度: 変更なし

各リンク座標基準に配置したオブジェクトを選択して「選択オブジェクトをエクスポート」コマンドから DAE (Collada) か STL 形式でエクスポートします.

Rhinoceros から DAE (Collada) をエクスポートする場合はエクスポートオプションにて
ジオメトリのみを保存」のみにチェック
を入れてファイルを書き出します.

この「ジオメトリのみを保存」でも色や単位情報も保存されます.

今回は表示(visual)用に色付きの DAE ファイルとしてエクスポートし,干渉チェック(collision)用にデータ量を少なくするため粗目の設定で STL ファイルをエクスポートしました.

  • 表示 visual 用 DAE ファイル
    • main-body.dae
    • door.dae
    • tray.dae
  • 干渉チェック collision 用 STL ファイル
    • main-body.stl
    • door.stl
    • tray.stl

リンク機構を含む URDF モデルファイルの作成

DAE や STL のメッシュデータのエクスポートが終わったらリンク機構を含む URDF モデルファイルを作成します.

ファイル配置

3dmodeling-examples/models/urdf/
├── meshes
│   └── washing-machine
│       ├── base_link.dae
│       ├── base_link.stl
│       ├── door.dae
│       ├── door.stl
│       ├── main-body.dae
│       ├── main-body.stl
│       ├── tray.dae
│       └── tray.stl
├── washing-machine_links.urdf
└── washing-machine.urdf

前回作成してメッシュファイルを配置したフォルダ
3dmodeling-examples/models/urdf/meshes
内にエクスポートしたメッシュファイルを配置します.

そしてフォルダ 3dmodeling-examples/models/urdf/ にファイル washing-machine_links.urdf をテキストファイルとして作成してそこにリンク機構を含む URDF モデルを作り込みます.前回作成したファイル washing-machine.urdf を複製してファイルの名前を変更しても良いです.

リンクの相対姿勢・可動域の確認

URDF データで 「洗濯機本体(main-body)」 と 「洗濯槽の扉(door)」 および 「洗剤投入トレイ(tray)」 それぞれの相対的な姿勢の関係とそれぞれの可動域を 「関節(joint)」 として定義しますので, CAD ( Rhinoceros など ) 上で相対姿勢および可動域の測定を行います.

その際,各リンクオブジェクトについて DAE や STL ファイルへのエクスポート用に原点へ移動した配置ではなく元々の洗濯機全体内での配置で調べるということに注意してください.

まずは 「洗濯機本体(main-body)」 と並進的な相対位置関係にある 「洗剤投入トレイ(tray)」 の座標の確認と設定可能な可動域を調べます.

  • 洗濯機本体(main-body) → 洗剤投入トレイ(tray)
    • 相対位置: 洗濯機本体原点から ( 0.0 , -190.00 , 956.00 ) [mm] の位置
    • 相対角度: ゼロ
    • 可動域: 前方へ 200 [mm] とした

次に 「洗濯機本体(main-body)」 と 「洗濯槽の扉(door)」 の相対座標・角度やの確認と設定可能な可動域を調べます.

扉は傾いて洗濯機本体に取り付けられているのでその角度とヒンジ回りの可動域も調べます.Rhinoceros では角度表示がラジアンでもできるのでそれを利用します.

  • 洗濯機本体(main-body) → 洗濯槽の扉(door)
    • 相対位置: 洗濯機本体原点から ( 306.58 , -258.00 , 675.82 ) [mm] の位置
    • 相対角度: 洗濯槽の扉(door)原点から Y軸と平行な軸 回りに 0.1396 [rad] (= 8.0 [deg] )
    • 可動域: ヒンジを軸に閉じた状態から 1.8326 [rad] (= 105 [deg] ) 開くとした

URDF データの作成

リンクモデルに必要なメッシュファイルと情報が揃いましたので URDF ファイルに書き込んだものが次のようになります.

washing-machine_links.urdf
<?xml version="1.0" ?>

<robot name="washing-machine">
  
  <link name="base_link">
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <mesh filename="package://3dmodeling-examples/models/urdf/meshes/washing-machine/main-body.stl" scale="0.001 0.001 0.001" />
      </geometry>
    </collision>
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <mesh filename="package://3dmodeling-examples/models/urdf/meshes/washing-machine/main-body.dae" />
      </geometry>
    </visual>
  </link>
  
  <link name="door">
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <mesh filename="package://3dmodeling-examples/models/urdf/meshes/washing-machine/door.stl" scale="0.001 0.001 0.001" />
      </geometry>
    </collision>
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <mesh filename="package://3dmodeling-examples/models/urdf/meshes/washing-machine/door.dae" />
      </geometry>
    </visual>
  </link>
  
  <link name="tray">
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <mesh filename="package://3dmodeling-examples/models/urdf/meshes/washing-machine/tray.stl" scale="0.001 0.001 0.001" />
      </geometry>
    </collision>
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <mesh filename="package://3dmodeling-examples/models/urdf/meshes/washing-machine/tray.dae" />
      </geometry>
    </visual>
  </link>
  
  <joint name="joint1" type="revolute">
    <parent link="base_link"/>
    <child  link="door"/>
    <origin xyz="0.30658 -0.258 0.67582" rpy="0 -0.1396 0"/>
    <axis xyz="0 0 1" />
    <limit effort="30" velocity="1.0" lower="-1.8326" upper="0.0" />
  </joint>
  
  <joint name="joint2" type="prismatic">
    <parent link="base_link"/>
    <child  link="tray"/>
    <origin xyz="0.0 -0.190 0.956" rpy="0 0 0"/>
    <axis xyz="1 0 0" />
    <limit effort="30" velocity="1.0" lower="0.0" upper="0.200" />
  </joint>
  
</robot>

washing-machine_links.urdf 内のそれぞれの要素について説明します.

  • 5〜48行 <link> 要素: リンクの定義 3つ ( base_link, door, tray )
    • <collision> 要素にメッシュに STL ファイルを使用し単位変換 [mm] → [m]
    • <visual> 要素に DAE メッシュファイルを使用
    • <origin> はリンク内のメッシュの配置なので今回は全てゼロ
  • 50〜56行 <joint> 要素: joint1
    • type で関節形式 revolve (=回転)を設定
    • <parent> 要素で関節を介する親リンク base_link を指定
    • <child> 要素で関節を介する子リンク door を指定
    • <origin> 要素で親子リンク間の相対座標
      • 位置の単位はメートル [m]
      • 姿勢角の正負に注意(座標軸に対して右ねじの法則)
    • <axis> 要素で revolve 関節の回転軸のリンク座標系での方向を設定
    • <limit> 要素
      • effort : 最大トルク – 今回はとりあえずの値
      • velocity : 最大角速度 – 今回はとりあえずの値
      • lower : 可動域下限 – 回転関節なので下限角度で単位はラジアン [rad]
      • upper : 可動域上限 – 回転関節なので上限角度で単位はラジアン [rad]
  • 58〜64行 <joint> 要素: joint2
    • type で関節形式 prismatic (=並進)を設定
    • <parent> 要素で関節を介する親リンク base_link を指定
    • <child> 要素で関節を介する子リンク tray を指定
    • <origin> 要素で親子リンク間の相対座標
    • <axis> 要素で prismatic 関節のリンク座標系での移動方向を設定
    • <limit> 要素
      • effort : 最大力 – 今回はとりあえずの値
      • velocity : 最大速度 – 今回はとりあえずの値
      • lower : 可動域下限 – 並進関節なので下限位置で単位はメートル [m]
      • upper : 可動域上限 – 並進関節なので上限位置で単位はメートル [m]

下記リンク先の ROS Wiki に URDF ファイルの作成方法のチュートリアルがありますので参考にしてください.

URDF モデルの確認

urdf_tutorialdisplay.launch で URDF モデル washing-machine_links.urdf の確認をします.(下記コマンド横スクロールで末尾まで表示)

$ roslaunch urdf_tutorial display.launch model:='$(find 3dmodeling-examples)/models/urdf/washing-machine_links.urdf'

URDF で <joint> 要素を定義して joint_state_publisher ウィンドウ内のスライドバーも有効になっているので関節を動かしてみます.

動画では表示メッシュの動きとともに TF も一緒に動いている様子が見られると思います.

今回の記事はここまでです.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(13)MoveIt の静モデルの作成

本シリーズ前回の記事 Gazebo/MoveIt のための 3D モデリング(12)Gazebo の静モデルの作成 ではエクスポートしたメッシュデータファイルを Gazebo の静モデルとしてモデルファイルに組み込んで表示する方法を紹介しました.

今回はエクスポートしたメッシュデータファイルを MoveIt の静モデルとしてモデルファイルに組み込んで表示する方法を紹介します.

今回紹介するのは次の2通りの方法です.メッシュを MoveIt GUI で読み込んで障害物とする方法とロボットモデル作成につながる方法の URDF モデルの作成とそれを確認表示する方法です.

  1. MoveIt 空間内の動作計画に対する障害物として STL ファイルまたは DAE ファイルを読み込んで設置
  2. URDF モデルの作成(→ ロボットリンクモデル作成へつながる)

MoveIt 内の障害物モデルとしてのメッシュ読込み

MoveIt には動作計画における障害物として STL ファイルや DAE ファイルをそのまま読み込んで MoveIt の動作計画空間内に配置する機能があります.

MoveIt の Motion Planning パネル内の Scene Objects タブを開いて, “Mesh from file” を選択します.

“Mesh from file” セレクタの右隣にあるプラスボタン [ + ] を押します.

(左図拡大は画像をクリック)

保存してある STL もしくは DAE ファイルを選択します.

ファイルを読み込むときに MoveIt の GUI インタフェースである RViz のメッセージウィンドウが開いて,ミリメートル単位で記述されているモデルをメートル単位に変換する旨の問いがなされるので [ Yes ] をクリックします.

読み込んだモデルをインタラクティブマーカや座標などを指定して意図した位置に設置し,左のチェックボックスをクリックすると設置リンクの選択を促されますので適宜選択して [ OK ] ボタンを押します.

次に [ Publish ] ボタンを押すと読み込んで設置したモデルが MoveIt 空間内で障害物として認識されます.

あとは [ Plan ] や [ Plan & Execute ] などで動作計画を実行するとその経路上に障害物があるとそれを避けたマニピュレーションの軌道が生成されます.

MoveIt モデル URDF ファイルの作成

MoveIt モデルの URDF ファイルの作成は下記リンク先の ROS Wiki に書かれています.
本記事ではそれらから MoveIt 静モデル作成に絞って説明します.

MoveIt モデルの作成にあたっては ROS パッケージを作成してその中にモデルの URDF ファイルを置くのが本記事の内容に続く応用も含めると一番簡便なのではないかと思います.

今回 ROS パッケージをつくるのが面倒なようでしたら下記リンク先リポジトリをクローンして利用してください.

3dmodeling-examples/
├── CMakeLists.txt
├── images
│   ├── front_view_win.png
│   ├── left_view_win.png
│   ├── top_view_win.png
│   ├── washing-machine_catalogue.pdf
│   └── washing-machine_catalogue.png
├── launch
│   ├── spawn-washingmachine.launch
│   └── world-washingmachine.launch
├── LICENSE
├── models
│   ├── gazebo_models
│   │   ├── washing-machine
│   │   │   ├── meshes
│   │   │   │   ├── base_link_blue-gray.stl
│   │   │   │   ├── base_link_dark-gray.stl
│   │   │   │   ├── base_link_gray-white.stl
│   │   │   │   ├── base_link_light-gray.stl
│   │   │   │   └── base_link.stl
│   │   │   ├── model.config
│   │   │   └── model.sdf
│   │   └── washing-machine-dae
│   │       ├── meshes
│   │       │   ├── base_link.dae
│   │       │   └── base_link.stl
│   │       ├── model.config
│   │       └── model.sdf
│   ├── urdf
│   │   ├── meshes
│   │   │   └── washing-machine
│   │   │       ├── base_link.dae
│   │   │       └── base_link.stl
│   │   └── washing-machine.urdf
│   └── washing-machine.3dm
├── package.xml
├── README.md
└── worlds
    └── washing-machine.world

本記事執筆時のサンプルモデルパッケージは右に示すような構成になっていますので参考にしてみてください.

この中の MoveIt URDF モデルに関連するフォルダ・ファイルがハイライトされた部分です.

meshes フォルダに base_link.dae と base_link.stl の2つのファイルが含まれていますが,これはサンプルのためですのでどちらか1つのファイルだけでも URDF モデルは作成できます.

今回の MoveIt 静モデルのサンプル URDF ファイル washing-machine.urdf の中身は次のようになっています.

<?xml version="1.0" ?>

<robot name="washing-machine">
  
  <link name="base_link">
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <mesh filename="package://3dmodeling-examples/models/urdf/meshes/washing-machine/base_link.stl" scale="0.001 0.001 0.001" />
      </geometry>
    </collision>
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <mesh filename="package://3dmodeling-examples/models/urdf/meshes/washing-machine/base_link.dae" />
      </geometry>
    </visual>
  </link>
  
</robot>

URDF のデータ形式は XML で,モデル構成要素の linkcollisiongeometrymesh が記述されています.今回は静モデルですので link 要素は1つですがロボットのように関節が多いとリンク数にともなって link 要素およびそこに含まれる子要素も増えます.

ROS パッケージ化していることで ROS のファイルシステムが名前空間から package://3dmodeling-examples/... によって 3dmodeling-examples パッケージのディレクトリを自動解決できるようになっています.

collision メッシュの STL データは単位情報を持っていないので scale="0.001 0.001 0.001" で 0.001倍(=1/1000) 変換をして [mm] のデータを [m] に換算しています.

  • 注) 表示上コードの右の方に隠れていると思うので横スクロールで確認してください.

visual のメッシュは DAE ファイルを利用していて,DAE モデルは単位情報を持っていて読み込む側でスケール判断をするので scale は必要ありません.

URDF モデルファイルの RViz 表示

URDF モデルの確認には urdf_tutorial パッケージの display.launch を利用します.

urdf_tutorial は joint-state-publisher-gui パッケージをインストールすることで利用できるようになります.下記は ROS Melodic の場合のインストールコマンドですので他の ROS バージョンの場合は melodic の部分を noetic などに書き換えて実行してください.

$ sudo apt update
$ sudo apt install ros-melodic-joint-state-publisher-gui

ターミナルで ROS 環境の設定と urdf_tutorial の display.launch の起動を行います.

$ source ~/$PathToYourWorkspace/devel/setup.bash
$ roslaunch urdf_tutorial display.launch model:='$(find 3dmodeling-examples)/models/urdf/washing-machine.urdf'
  • 注)
    • $PathToYourWorkspace は各自の ROS ワークスペースへのパスを記述
    • Launch オプションの model:= には URDF ファイルパスを指定
      • 上記は 3dmodeling-examples パッケージ内のフォルダに washing-machine.urdf がある場合の例

正常に実行できると次の図のように洗濯機モデルが RViz 空間上に表示されます.

今回の単一リンクの正モデルの場合はリンク(フレーム)間の座標変換(tf)が無いので RViz 内の “Global Status: Warn / Fixed Frame No tf data. Actual error: Fixed Frame does not exist” と警告が出ますが問題はありません.
問題はありませんが気持ちの収まりが悪いようでしたらターミナルを追加で立ち上げて下記コマンドで例えば /world フレームから洗濯機モデルの /base_link フレームへの tf をパブリッシュすると警告が消えます.

$ rosrun tf2_ros static_transform_publisher 0 0 0 0 0 0 /world /base_link

今回の記事はここまでです.


本シリーズ次回の記事は洗濯機の URDF モデルにドアのヒンジなどの動く箇所を設定してより機械らしい(ロボットに近い)モデルにする様子を紹介する予定です.

著者:yamamoto.yosuke

“ROS 入門向けマニピュレータ導入検証” ROS Melodic & Noetic 対応更新

本ブログ記事「ROS 入門向けマニピュレータ導入検証」の公開後に OpenManipulator-X のソフトウェア構成の変更があり,記事で紹介した手順のアップデートが必要でしたので本記事にて紹介します.

基本的には ROBOTIS の e-Manual が充実していますのでその紹介ですが,加えて MoveIt での動作にフォーカスしたまとめと,「ROS 入門向けマニピュレータ導入検証」の MoveIt Commander Python サンプルスクリプトを実行する際の手順や入門者向けの注意点などをまとめています.

1. システム構成

今回動作検証したシステム構成は次のようになっています.

  • OS + ROS
    1. Ubuntu 18.04 + ROS Melodic
    2. Ubuntu 20.04 + ROS Noetic
  • OpenManipulator-X 実機インタフェース: OpenCR

2. インストールなどの準備

インストールなどの手順は ROS Noetic については ROBOTIS の e-Manual の “Noetic” バージョンでの内容のとおりです.

ROS Melodic については ROBOTIS の e-Manual に記述は見つけられませんでしたがソフトウェアパッケージなどは準備されていましたので e-Manual の “kinetic” や “noetic” の部分を “melodic” に読み替えることで問題なくインストールなどの準備ができました.

2.1 ROS パッケージのインストール

2020年2月の時点からはパッケージ構成が変更されていましたので現時点で必要なパッケージをインストールやソースのビルドを行います.

2.2 通信インタフェースとして OpenCR を利用

通信インタフェースとして OpenCR を利用する場合は次のインストールなどの設定を行います.

2.3 MoveIt パッケージのインストール

OpenManipulator-X の MoveIt パッケージは “Experimental” の位置づけとのことです.

3. MoveIt 実行手順

MoveIt の実行についても ROBOTIS の e-Manual の内容のほぼそのままですが,実機動作のときはインタフェースに OpenCR を利用しましたので launch ファイルの起動オプションで USB ポートを指定する必要がありました.

3.1 Gazebo シミュレータ と MoveIt

ターミナル 1
$ roslaunch open_manipulator_controllers joint_trajectory_controller.launch
  • 注)
    • Gazebo の Play ボタンを手動で押してシミュレーションを開始する必要あり
      • “Click on Play ▶ button at the bottom of the Gazebo simulator.”
      • → Gazebo シミュレータが起動すると MoveIt が動作を開始

3.2 実機 と MoveIt(OpenCR インタフェース)

ターミナル 1
$ roslaunch open_manipulator_controllers joint_trajectory_controller.launch sim:=false usb_port:=/dev/ttyACM0

3.3 MoveIt Commander のサンプル Python スクリプト

ブログ記事「ROS 入門向けマニピュレータ導入検証」で検証したシステムの Ubuntu 16.04 + ROS Kinetic または Ubuntu 18.04 + ROS Melodic では Python 2 を使用していました.

今回 Ubuntu 18.04 + ROS Melodic のシステムでこのサンプルプログラムを実行させる場合は元のプログラムのまま実行できます.

Ubuntu 20.04 + ROS Noetic のシステムでは基本的に Python 3 を使用しますので Python 2 向けのサンプルプログラムを Python 3 に対応させるように変更する必要があります.

Python 2 から Python 3 への変更は下記リンク先 ROS Wiki – Source code changes to support Python 3 のドキュメントに推奨される方法が記述されています.

ただ推奨されている方法は少し手順が多いので,ここでは簡易的に記事内の「Open Manipulator X – MoveIt Commander テストプログラム」を Ubuntu 20.04 + ROS Noetic の Python 3 で実行するためにサンプルプログラムの 1行目 の pythonpyrhon3 に書き換えて対応します.

#!/usr/bin/env python
↓ 1行目 の pythonpyrhon3 に変更
#!/usr/bin/env python3

サンプルプログラムは今回はインストール時にワークスペースにクローンした open_manipulator_controls パッケージ内の /open_manipulator_controllers フォルダ下に /script フォルダを新規作成して,そこにファイル名 open_manipulator_moveit_tutorial_poses.py で実行可能ファイルとして保存しました.

  • 注1)
    • 今回の ROBOTIS e-Manual のインストール手順に則っている場合の保存先ファイルは ~/catkin_ws/src/open_manipulator_controls/open_manipulator_controllers/script/
      open_manipulator_moveit_tutorial_poses.py
  • 注2)
    • ファイルへの実行権限の付与コマンドは
      $ chmod 777 open_manipulator_moveit_tutorial_poses.py

Python スクリプトから MoveIt を動作させるコマンドインタフェースの MoveIt Commander の実行には MoveIt が起動している必要がありますので 1つ目のターミナル で MoveIt(対象は実機ロボットもしくは Gazebo シミュレータ)を実行した状態で,2つ目のターミナル で Python スクリプトを実行します.

ターミナル 1(対象: Gazebo シミュレータ)
$ roslaunch open_manipulator_controllers joint_trajectory_controller.launch

もしくは

ターミナル 1(対象: 実機ロボットsim:=false / OpenCR ポート設定usb_port:=/dev/ttyACM0
$ roslaunch open_manipulator_controllers joint_trajectory_controller.launch sim:=false usb_port:=/dev/ttyACM0

ROS ではターミナルを新規に開くたびに ROS 環境の設定が必要です.
今回のケースでは ROS ワークスペース内のソフトウェアパッケージも利用しているので
$ source $PathToYourWorkspace/devel/setup.bash で環境設定します.

  • 注)
    • $PathToYourWorkspace は各自の ROS ワークスペースへのパスを記述
    • 今回の ROBOTIS e-Manual のインストール手順に則っている場合は $ source ~/catkin_ws/devel/setup.bash
ターミナル 2(ROS 環境の設定・ターミナル起動時に1回)
$ source $PathToYourWorkspace/devel/setup.bash

先程 open_manipulator_controls パッケージ内にファイル保存と実行権限設定をしたサンプル Python スクリプト open_manipulator_moveit_tutorial_poses.py を実行します.

ターミナル 2(Python スクリプトの実行)
$ rosrun open_manipulator_controllers open_manipulator_moveit_tutorial_poses.py

以上です.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(12)Gazebo の静モデルの作成

本シリーズ前回の記事 Gazebo/MoveIt のための 3D モデリング(11)形状データのエクスポート で Gazebo や MoveIt で利用できるデータ形式にエクスポートする手順を紹介しました.

今回はエクスポートしたメッシュデータファイルを Gazebo の静モデルとしてモデルファイルに組み込んで表示する方法を紹介します.

前回の記事の予告では「Gazebo/MoveIt のための 3D モデリング(12)Gazebo や MoveIt の静モデルの作成」と Gazebo と MoveIt の両方の作成方法を紹介する予定でしたが本記事では Gazebo モデルの作成を紹介して,次回に MoveIt の静モデルの作成を紹介します.

Gazebo モデル SDF ファイルの作成

今回紹介する Gazebo モデルを構成するファイル群は次のようなフォルダ構造にしています.

washing-machine
├── meshes
│   ├── base_link_blue-gray.stl
│   ├── base_link_dark-gray.stl
│   ├── base_link_gray-white.stl
│   ├── base_link_light-gray.stl
│   └── base_link.stl
├── model.config
└── model.sdf

Gazebo モデルは下記リンク先の GitHub リポジトリから入手可能です.

model.sdf

まずは Gazebo SDF モデルの本体のファイルとも言える model.sdf の中身を見てみます.XML データになっていて干渉チェック用のモデルを記述した collision や視覚表示用のモデルを記述した visual などの要素があります.

<?xml version="1.0" ?>
<sdf version="1.6">
  <model name="washing-machine">
    <!--static>true</static-->
    <pose>0 0 0 0 0 0</pose>
    <link name="base_link">
      <inertial>
        <mass>10.0</mass>
        <inertia>
          <ixx>1.000000</ixx>
          <ixy>0.000000</ixy>
          <ixz>0.000000</ixz>
          <iyy>1.000000</iyy>
          <iyz>0.000000</iyz>
          <izz>1.000000</izz>
        </inertia>
      </inertial>
      <collision name="collision">
        <geometry>
          <mesh>
            <uri>model://washing-machine/meshes/base_link.stl</uri>
            <scale>0.001 0.001 0.001</scale>
          </mesh>
        </geometry>
      </collision>
      <visual name="visual-gray-white">
        <geometry>
          <mesh>
            <uri>model://washing-machine/meshes/base_link_gray-white.stl</uri>
            <scale>0.001 0.001 0.001</scale>
          </mesh>
        </geometry>
        <material>
          <ambient>  0.95 0.95 0.95 1.0</ambient>
          <diffuse>  0.95 0.95 0.95 1.0</diffuse>
          <specular> 0.95 0.95 0.95 1.0</specular>
        </material>
      </visual>
      <visual name="visual-light-gray">
        <geometry>
          <mesh>
            <uri>model://washing-machine/meshes/base_link_light-gray.stl</uri>
            <scale>0.001 0.001 0.001</scale>
          </mesh>
        </geometry>
        <material>
          <ambient>  0.80 0.80 0.80 1.0</ambient>
          <diffuse>  0.80 0.80 0.80 1.0</diffuse>
          <specular> 0.80 0.80 0.80 1.0</specular>
        </material>
      </visual>
      <visual name="visual-dark-gray">
        <geometry>
          <mesh>
            <uri>model://washing-machine/meshes/base_link_dark-gray.stl</uri>
            <scale>0.001 0.001 0.001</scale>
          </mesh>
        </geometry>
        <material>
          <ambient>  0.45 0.45 0.45 1.0</ambient>
          <diffuse>  0.45 0.45 0.45 1.0</diffuse>
          <specular> 0.45 0.45 0.45 1.0</specular>
        </material>
      </visual>
      <visual name="visual-blue-gray">
        <geometry>
          <mesh>
            <uri>model://washing-machine/meshes/base_link_blue-gray.stl</uri>
            <scale>0.001 0.001 0.001</scale>
          </mesh>
        </geometry>
        <material>
          <ambient>  0.55 0.65 0.75 0.6</ambient>
          <diffuse>  0.55 0.65 0.75 0.6</diffuse>
          <specular> 0.55 0.65 0.75 0.6</specular>
        </material>
      </visual>
    </link>
  </model>
</sdf>

STL ファイルは単位の情報を持っていません.本シリーズの記事では Rhinoceros 上で 単位 [mm] にてモデルを作成してメッシュを出力したので, [mm] での数値を Gazebo 上の単位 [m] に変換するために mesh 要素の scale の (X,Y,Z) に全て 0.001 (=1/1000) を設定しています.適切なスケーリングを忘れたり,不要なスケーリングをしてしまったりするとモデルにメッシュは読み込めているのにありえない大きさのメッシュになってしまって目視できなくなるようなことがあるので注意が必要です.

visual 要素は色ごとに複数作成して各色のメッシュのファイルをそれぞれの uri 要素に記述して materialambient(=環境反射率) diffuse(=拡散反射率) specular(=鏡面反射率) の数値を RGBA(=赤,緑,青,透明度) の順で設定します.(「透明度」は数値的には「不透明度」を意味するように思うのですが「透明度」と一般的には言われているようです.)

collision 要素はメッシュに洗濯機全体の閉じたメッシュ群である base_link.stl ファイルを使用しています.

inertialmass(=質量) inertia(=慣性モーメント) は今回はメッシュの確認が主な目的ですので適当な値を設定しました.精度良く物理シミュレーションを行いたい場合は適切な値を設定する必要があります.

model.config

model.config ファイルの中身も XML データ形式で記述されていて当該 Gazebo モデルのメタ的な情報を記述しています.

<?xml version="1.0"?>

<model>
  <name>washing-machine</name>
  <version>1.0</version>
  <sdf version="1.6">model.sdf</sdf>
  
  <author>
    <name>Tokyo Opensource Robotics Kyokai Association</name>
    <email>info@opensource-robotics.tokyo.jp</email>
  </author>
  
  <description>
    A Model Example of a Washing Machine.
  </description>
  
</model>

SDF モデルファイルの Gazebo への読み込み

Gazebo の 3D モデルをシミュレーション空間上に読込・表示する方法は主に次の3つの方法があります.

  1. .gazebo/models フォルダにコピー
  2. world ファイルを作成
  3. プログラムで配置(Spawn)

表示方法-1 .gazebo/models フォルダにコピー

本記事における SDF モデルフォルダ washing-machine をホームディレクトリ直下の隠しフォルダ .gazebo/models 内にコピーして Gazebo のウインドウ内の操作で読み込みます.
( USERNAME の部分は各自のユーザ名に読み替えてください.)

/home/USERNAME/.gazebo/models
├── ambulance
│   ├── materials
│   │   └── textures
│   │       └── ambulance.png
│   ├── meshes
│   │   ├── ambulance.mtl
│   │   └── ambulance.obj
│   ├── model.config
│   └── model.sdf
  :
(中略)
  :
└── washing-machine
    ├── meshes
    │   ├── base_link_blue-gray.stl
    │   ├── base_link_dark-gray.stl
    │   ├── base_link_gray-white.stl
    │   ├── base_link_light-gray.stl
    │   └── base_link.stl
    ├── model.config
    └── model.sdf

.gazebo/models 内にモデルをコピーすると Gazebo ウィンドウ内に挿入するモデルの選択肢の1つとして提示されるようになります.

  1. Gazebo を起動
    コマンド入力は $ gazebo
  2. Gazebo ウィンドウ
    → Insert タブ
    → /home/USERNAME/.gazebo/models
    → washing-machine をクリック
  3. Gazebo の 3D 空間内の地面上の1点を
    クリックして洗濯機モデルを配置

(図: クリックで拡大)

このような Gazebo 内に手作業でモデルを読み込む方法でもシミュレーションはできるのですが,自動的に読み込む手段として world ファイルを作成する方法や launch ファイル内やプログラムからモデルを読み込んで Gazebo に表示させる方法があります.自動的に読み込む方がモデルを用いた Gazebo シミュレーションでロボットを動作させるような段階では毎回手作業でモデルを Gazebo 空間内に読み込む必要がなく楽です.

表示方法-2 world ファイルを作成

前項目の「表示方法-1 .gazebo/models フォルダにコピー」は Gazebo を起動するたびにモデルを配置する必要があります.モデルの確認には十分ですが Gazebo のシミュレーションでロボットの動作を同じ環境で何度も試行するには向いていません.そこで Gazebo 環境の定義をする world ファイルを読み込むことで常に同じ環境を再現することができます.

world ファイルも XML 形式のデータです.

<?xml version="1.0" ?>
<sdf version="1.4">
  <world name="default">
    <include>
      <uri>model://ground_plane</uri>
    </include>
    <include>
      <uri>model://sun</uri>
    </include>
    <include>
      <uri>model://washing-machine</uri>
      <name>washing-machine</name>
      <pose>0 0 0 0 0 0</pose>
    </include>
    
<!--
    <include>
      <uri>model://washing-machine-dae</uri>
      <name>washing-machine-dae</name>
      <pose>0 1.0 0 0 0 0</pose>
    </include>
-->
    
  </world>
</sdf>

上記 world ファイルでは ground_plane と sun,washing-machine モデルを Gazebo 空間に配置しています.16〜22行 はコメントアウト行で後述の dae ファイルを利用した場合の Gazebo モデルを配置する設定でそれを無効にしています.

world ファイルを読み込んで Gazebo を起動する launch ファイルを作成します.

<launch>
  
  <include file="$(find gazebo_ros)/launch/empty_world.launch">
    <arg name="world_name"   value="$(find 3dmodeling-examples)/worlds/washing-machine.world"/>
    <arg name="paused"       value="false"/>
    <arg name="use_sim_time" value="true"/>
    <arg name="gui"          value="true"/>
    <arg name="recording"    value="false"/>
    <arg name="debug"        value="true"/>
  </include>
  
</launch>

今回作例を置いている ROS パッケージ内での world ファイルや launch ファイルのディレクトリ構造は次のようになっています.

3dmodeling-examples/
├── CMakeLists.txt
├── images
│   ├── front_view_win.png
│   ├── left_view_win.png
│   ├── top_view_win.png
│   ├── washing-machine_catalogue.pdf
│   └── washing-machine_catalogue.png
├── launch
│   ├── spawn-washingmachine.launch
│   └── world-washingmachine.launch
├── LICENSE
├── models
│   ├── gazebo_models
│   │   ├── washing-machine
│   │   │   ├── meshes
│   │   │   │   ├── base_link_blue-gray.stl
│   │   │   │   ├── base_link_dark-gray.stl
│   │   │   │   ├── base_link_gray-white.stl
│   │   │   │   ├── base_link_light-gray.stl
│   │   │   │   └── base_link.stl
│   │   │   ├── model.config
│   │   │   └── model.sdf
│   │   └── washing-machine-dae
│   │       ├── meshes
│   │       │   ├── base_link.dae
│   │       │   └── base_link.stl
│   │       ├── model.config
│   │       └── model.sdf
│   ├── urdf
│   │   ├── meshes
│   │   │   └── washing-machine
│   │   │       ├── base_link.dae
│   │   │       └── base_link.stl
│   │   └── washing-machine.urdf
│   └── washing-machine.3dm
├── package.xml
├── README.md
└── worlds
    └── washing-machine.world

また,この際に必要なことが Ubuntu の環境変数に world や Gazebo のモデルのあるディレクトリのパスを通しておくことです.ROS パッケージの package.xml に Gazebo モデルへのパスと実行依存関係を記述します.

  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>urdf</build_depend>
  
  <build_export_depend>urdf</build_export_depend>
  
  <exec_depend>urdf</exec_depend>
  <exec_depend>gazebo_ros</exec_depend>

  <!-- The export tag contains other, unspecified, tags -->
  <export>
    <gazebo_ros gazebo_model_path="${prefix}/models/gazebo_models"/>
    <gazebo_ros gazebo_media_path="${prefix}/worlds"/>
  </export>
  
</package>

package.xml に記述した内容を有効にするためにビルドしてワークスペースの環境設定 source devel/setup.bash をします.次のコマンドは Gazebo モデルを含んだワークスペース robotmodels_ws にてビルドと環境設定を行った場合の例です.

robotuser@robotuser-PC:~/robotmodels_ws$ catkin_make
robotuser@robotuser-PC:~/robotmodels_ws$ source devel/setup.bash

world ファイルを読み込むように作成した launch ファイルを実行して Gazebo を起動します.

$ roslaunch 3dmodeling-examples world-washingmachine.launch

表示方法-3 プログラムで配置(Spawn)

前項目の「表示方法-2 world ファイルを作成」では world ファイルでモデルの配置が決められた状態で Gazebo を起動していました. Spawn を行うことで既に実行されている Gazebo シミュレーション空間内に位置・姿勢を指定してモデルを配置することができます.

次に Spawn を用いた場合の launch ファイルの例を示します.

<launch>
  
  <include file="$(find gazebo_ros)/launch/empty_world.launch">
    <arg name="paused"       value="false"/>
    <arg name="use_sim_time" value="true"/>
    <arg name="gui"          value="true"/>
    <arg name="recording"    value="false"/>
    <arg name="debug"        value="true"/>
  </include>
  
  <!-- Spawn a robot into Gazebo -->
  <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model" 
        args="-file $(find 3dmodeling-examples)/models/gazebo_models/washing-machine/model.sdf -sdf -x 0.0 -y 2.0 -z 0.0 -R 0.0 -P 0.0 -Y 0.0 -model washing-machine" />
  
</launch>

Gazebo が empty_world.launch で実行開始された後 spawn_model を用いて Gazebo シミュレーション空間上の ( 0.0, 2.0, 0.0 ) の位置に配置しています.

spawn-washingmachine.launch を実行してみます.

$ roslaunch 3dmodeling-examples spawn-washingmachine.launch

world ファイルを読み込んで Gazebo を起動したときと異なる位置に配置しています.

Gazebo モデルにおける STL ファイルと DAE ファイルの表示比較

Gazebo で dae ファイルを用いて色付きモデルを表示することも可能ですが,STL ファイルを用いて SDF ファイル内で色設定をした方が Gazebo 表示状態を確認してから SDF ファイル内で色や透明度の調整が可能ですので意図した表示になるように思います.

上の図の状態は先述の washing-machine.world ファイル内のコメントアウトを行っている 16行目 と 22行目 を削除してファイルを保存し, world ファイルを読み込んで Gazebo を実行すると表示されます.

<?xml version="1.0" ?>
<sdf version="1.4">
  <world name="default">
    <include>
      <uri>model://ground_plane</uri>
    </include>
    <include>
      <uri>model://sun</uri>
    </include>
    <include>
      <uri>model://washing-machine</uri>
      <name>washing-machine</name>
      <pose>0 0 0 0 0 0</pose>
    </include>
    
<!--
    <include>
      <uri>model://washing-machine-dae</uri>
      <name>washing-machine-dae</name>
      <pose>0 1.0 0 0 0 0</pose>
    </include>
-->
    
  </world>
</sdf>

Gazebo シミュレーションの様子をデモンストレーションなどで見せるような場合に,DAE ファイルを使った Gazebo モデルで見た目がいまいちに感じたら STL ファイルと SDF ファイル内での色指定を試してみてはいかがでしょうか.

今回の記事はここまでです.


本シリーズ次回の記事は

「Gazebo/MoveIt のための 3D モデリング(13)MoveIt の静モデルの作成」

として,前回エクスポートしたメッシュデータファイルを MoveIt のモデルファイルに組み込んで表示を行う様子を紹介する予定です.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(11)形状データのエクスポート

本シリーズ前回の記事 Gazebo/MoveIt のための 3D モデリング(10)部品作成編 で洗濯機全体の形状が完成しました.

まずはエクスポートするメッシュの確認も兼ねて,可動部分のない一番単純な Gazebo と MoveIt それぞれにおける洗濯機モデルを作ることを目標として,今回は CAD( 本記事では Rhinoceros )の形状データに色を付けるとともに Gazebo や MoveIt で利用できるデータ形式にエクスポートする手順を紹介します.

次の図は今回エクスポートするデータを Gazebo と MoveIt のシミュレーションモデルに組み込んで表示させた様子で,次回の記事のゴールになる予定です.

Gazebo/MoveIt モデルに必要なメッシュデータ

Gazebo/MoveIt のシミュレーションモデルにはディスプレイ表示用の visual メッシュと干渉チェック用の collision メッシュの2種類のデータが必要です.今回は大きく分けて次の ① ② ③ の 3種類 のデータを用意します.

  • MoveIt 用 URDF モデル
    • visual : Collada(DAE)データ ①
      • DAE ファイルには色と単位の情報も「含まれる」
    • collision : 物体領域を明確に分けられる閉じた STL メッシュ(群)データ ②
  • Gazebo 用 SDF モデル
    • visual : 各表示色別に分けた STL メッシュデータ ③(今回は4色4ファイル)
      • STL ファイルには色と単位の情報が「含まれない」ため SDF ファイル側で指定
    • collision : 物体領域を明確に分けられる閉じた STL メッシュ(群)データ ②

一般的には visual メッシュを少し細かく(データとしては重く)し,collision メッシュは粗く(データとしては軽く)することが多いです.Gazebo シミュレータは物理エンジンも含まれていますのでロボットの力制御シミュレーションをするようになってくると collision メッシュの方をより細かくする必要が出てくるかもしれません.

干渉チェック(collision)用 STL メッシュデータのエクスポート

ひとまず可動部分のない Gazebo および MoveIt のシミュレーションモデルを作成しますので,これまで作成してきた洗濯機モデルの閉じたポリサーフェス(=ソリッド)をそのまま全て選択して「選択オブジェクトをエクスポート(Export)」で干渉チェック用の STL メッシュデータファイルとしてエクスポートします.

  • 選択オブジェクトをエクスポート
    • メニュー: ファイル(F) > 選択オブジェクトをエクスポート(E)
    • コマンド: Export

ファイルの種類で「STL (Stereolithography) (*.stl)」を選択します.ファイル名はメッシュモデルの乗るシミュレーションモデルのリンク名にすると分かりやすいので今回は「base_link.stl」とします.

ポリゴンメッシュ詳細オプション」の子ウィンドウが出るので各設定項目は主に下のリストのように今回は設定しました.(図はクリックで拡大表示されます.)

  • 最小エッジ長さ(E): 0.0001
  • 最大エッジ長さ(L): 0.0 (=無指定)
  • エッジからサーフェスの最大距離(D): 0.1
  • メッシュをリファイン(R): チェック

STL メッシュデータの粗密やデータ量を調整したい場合は主にこれらの設定値を調整します.

STLエクスポートオプション」ではデータ量を確認してデータが大きすぎるような場合には「メッシュを調整」ボタンから再調整して,問題なければ「バイナリ(B)」でエクスポートします.

エクスポートした STL ファイルの内容を確認するにはフリーソフトウェアの MeshLab にインポートするのが良いのではないかと思います.

MeshLab は Windows・Mac・Linux のどのプラットフォームにもインストールできますので便利です.

また Windows であれば「3Dビューアー」,Mac であれば「プレビュー」でも STL ファイルを表示することが可能です.

エクスポートした STL ファイルに洗濯機全体の形状データが含まれているように表示されるかと思います.

干渉チェック(collision)用の STL ファイルへのエクスポート手順は以上です.

洗濯機各部の色付け

ディスプレイ表示用の visual メッシュは色を付けない単色での利用も可能ですが,せっかくなのでカタログから推測して次のリストの色分けをしてみます.

  • gray-white: 本体の前面・上面側面の大部分
  • light-gray: ドア枠と本体側周辺部品・背面
  • dark-gray: 底部周辺
  • blue-gray: ドアの透明窓部・液晶表示部

Rhinoceros のデフォルトではポリサーフェスで1つにまとまっていると色や反射率,透過率などの設定が含まれるマテリアル設定が1つしか反映されないので,色ごとのポリサーフェスやサーフェス,それらのグループに分解します.ソリッド(=閉じたポリサーフェス)の状態は残しておきたいので色付け用のレイヤを作成してそのレイヤに洗濯機モデル全体をコピーしたものを分解,色付けします.

色はオブジェクトの「マテリアル」を設定して付けます.

色ごとに分けたオブジェクト(=ポリサーフェスやサーフェス,グループ)を選択してから右クリックして「オブジェクトのプロパティ(S)」を表示して「プロパティ: マテリアル」タブを開きます.

マテリアルの設定時に気を付ける点があり,「金属」系の色は後の項目でエクスポートする Collada(DAE)ファイルや Gazebo,MoveIt のディスプレイ上では反映されなく,意図しない,おそらくエクスポートしたオブジェクトのあるレイヤー色か黒などに表示されてしまうので「プラスチック」系のマテリアルを使用して各色を指定するのが良さそうです.

また,Rhinoceros 上での表示形式を「レンダリング」にすることでマテリアルが反映された表示になります.

上の図では 「gray-white」を選択した例を示していますが,他の「light-gray」「dark-gray」「blue-gray」についても同様にプラスチック系マテリアルの色を調整して設定します.

Collada(DAE)メッシュデータのエクスポート

MoveIt シミュレーションモデルの URDF ファイルから表示用(visual)メッシュとして使うために,マテリアルを設定して色付けしたモデル Rhinoceros から Collada(DAE)ファイルとしてエクスポートします.

Collada(DAE)のメッシュデータには色や単位の情報も含まれるので全色分のオブジェクトを一緒くたに選択して「選択オブジェクトをエクスポート(Export)」でエクスポートします.

ファイルの種類に「COLLADA(*.dae)」を選択します.ファイル名はメッシュモデルの乗るシミュレーションモデルのリンク名にすると分かりやすいので今回は「base_link.dae」とします.

エクスポートした DAE ファイルの内容の確認は Mac だと「プレビュー」で右の図のように行えます.

FreeCAD は Windows や Mac,Linux で利用でき,DAE データも表示することができます.

FreeCAD の操作感はあまり良いとは言えないのですが様々な形式の 3D データが読み込めるのでデータ確認には非常に便利です.

金属マテリアル表現の 3D モデルについて

今回,洗濯機操作部の丸いボタンに金属系マテリアルを設定して Collada(DAE)ファイルとしてエクスポートして利用しようとしましたが金属マテリアル部分が DAE メッシュとしては黒色になってしまって金属的な表現にはなりませんでした.

今回筆者の調べた範囲においては glTF(ジー”エル”ティーエフ) 形式とそのバイナリ形式の glb 形式が 3D モデルのファイル内に金属やガラスなどのマテリアル表現の情報も含まれる形式とのことでしたので Rhioceros にこれらの形式をエクスポートするプラグインを導入し,丸ボタンに金属マテリアルを適用したものを glb 形式でエクスポートして,Web にある glTF ビューア で表示してみたものが次の図です.

ボタンの部分が金属的な表現になっているように見えます.

ではこの glTF や glb 形式の 3D モデルファイルが Gazebo や MoveIt で使えるのか,といったところが ROS ユーザとしては気になるところです.Gazebo や MoveIt はレンダリングエンジンに OGRE(Object-Oriented Graphics Rendering Engine) を利用しています.

OGRE の現時点で最新リリースが 2022年2月9日 リリースの 13.3 です.OGRE v13.3 では glTF2.0 形式の情報を利用した金属や布などのマテリアル表現が可能になったとのことです.

Gazebo や MoveIt でも OGRE で新しくリリースされた豊かなマテリアル表現機能を使えるように実装が進んだら,今回上手くいかなかった金属表現もできるようになるのかな?と期待しています.

色別 STL メッシュデータのエクスポート

Gazebo シミュレーションモデルの SDF ファイルから表示用(visual)メッシュを色付きで使うためには色ごとにメッシュを分けた STL データファイルに対して SDF ファイル内で色情報を指定する必要があります.

そのために Rhinoceros からは色ごとにオブジェクトを選択して各色の STL ファイルとしてエクスポートします.

一般的な STL ファイルには色情報が含まれませんので Rhinoceros 上で色を付ける必要性はないのですが,前の項目で既に色ごとに分けて色付けしたオブジェクトがありますので,ここでは「色で選択(SelColor)」でそれぞれの色を選択して「選択オブジェクトをエクスポート(Export)」すると楽にできます.

  • 色で選択
    • メニュー: 編集(E) > オブジェクトを選択(S) > 色で選択(C)
    • コマンド: SelColor

STL エクスポート自体のの手順は先ほどの「干渉チェック(collision)用 STL メッシュデータのエクスポート」内で行った手順と基本的には同じですが,色分けした,ソリッド(=閉じたポリサーフェス)ではない,開いたポリサーフェスやサーフェスをエクスポートすることが出てきますので,その場合は「STLエクスポートオプション」にて「開いたオブジェクトをエクスポート(E)」のチェックを入れる必要があります.

ファイル名はメッシュモデルの乗るシミュレーションモデルのリンク名に色名を足しておくと分かりやすいので,今回は下のリストの各ファイル名で 4色分 4つのファイルとしてエクスポートしました.

  • base_link_gray-white.stl
  • base_link_light-gray.stl
  • base_link_dark-gray.stl
  • base_link_blue-gray.stl

複数に分けた STL ファイルを MeshLab にインポートすると MeshLab 内のレイヤとして表示されるのでレイヤの表示・非表示を切り替えるなどしてメッシュの確認を行うと良いのではないかと思います.

今回の記事はここまでです.


本シリーズ次回の記事は

「Gazebo/MoveIt のための 3D モデリング(12)Gazebo や MoveIt の静モデルの作成」

として,今回エクスポートしたメッシュデータファイルを Gazebo や MoveIt のモデルファイルに組み込んで表示する様子を紹介する予定です.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(10)部品作成編

前回は「Gazebo/MoveIt のための 3D モデリング(9)滑らかなサーフェス – 作成編(その4)」として,制御点の大幅な位置調整も含めた NURBS サーフェスの調整や制御点移動による曲面サーフェス作成例を紹介しました.前回の記事も含め,複数回に分けてサーフェスの作成方法に主眼を置いたモデリングの紹介をしてきました.

今回の記事は洗濯機の扉などの各部品の作成例を紹介して最後に洗濯機のモデル形状を完成させます.

洗濯槽扉とその周辺部品

洗濯機前面が球面サーフェスなので,円形の扉の中心軸をその球の中心から作ると洗濯槽扉と洗濯機前面共に軸対称となり幾何学的に収まりが良くなります.

側面図から判断し,球の中心から水平よりも 8° 上方に角度を持つ直線が扉の中心軸であると推定しました.

(以後,各図ともクリックで拡大します.)

洗濯槽扉は中心軸周りの「回転(Revolve)」で作成できますので,そのプロファイル曲線作成にあたりそれに適した「作業平面」を設定すると描画しやすいと思います.

洗濯槽扉の軸上の 2点 とワールド座標系での XZ平面内 の点の「3点指定」で「作業平面の設定」を行ってから,ビューを「作業平面の平行ビュー」を設定すると見やすいです.

  • 作業平面の設定(3点指定)
    • メニュー: ビュー(V) > 作業平面の設定(P) > 3点指定(3)
    • コマンド: CPlane > 3点(P)
  • 作業平面の平行ビュー
    • メニュー: ビュー(V) > ビューの設定(V) > 作業平面の平行ビュー(P)
    • コマンド: Plan

洗濯槽扉の奥行方向の大きさや形状は上面図に開いた状態の扉が描画されているので少し斜めから見た投影図になってしまいますがそこから推測します.

そのために既に 3D 空間上に配置されている上面図をコピーし,位置と方向を調整して,右の図の様に作業平面上で直接的に参考にできるように配置します.

洗濯槽扉の形状プロファイルを描画して「回転(Revolve)」などで作成します.

また洗濯機本体側の洗濯槽扉の収まる部分や洗濯槽については三面図などからは寸法は拾えないので推測で大体の形状で作成します.

洗濯槽扉が開く方向は三面図から洗濯槽扉の中心軸を通りワールド座標系の Y軸 に平行な平面内で回転して開いているようです.

この平面内で開いた状態の位置に洗濯槽扉をコピー移動・回転させることにより回転中心を幾何学的に算出することができます.

回転軸が算出できたら洗濯機本体に干渉しないヒンジのモデルを作成します.

洗濯槽扉を開くボタンは洗濯槽扉に正対する作業平面上で形状を描画してサーフェスにしてゆくと作業がしやすいです.

洗濯機上面給水部

カタログのレンダリング図を見ると給水口が洗濯機本体への接続部は段落ち形状になっています.

段落ち部の平面形状は上面図から,また給水口の大体の形状は三面図から拾えます.

段落ちしている寸法は三面図からは拾えませんがシミュレーション上も問題になる部分ではないので推測で適当に 10mm としました.

洗濯機前面下部フタ

洗濯機前面下部のフィルターが入っているところのフタは雰囲気を出すだけで溝を設ける形にしました.

洗濯機両側面の取っ手

洗濯機両側面の移設用の取っ手は側面図と正面図から大きさや出っ張り高さは分かりますが取っ手部分の形状はレンダリング画像から推測するしかないので大体の形状でモデリングします.

洗濯機を設置するロボットのような取っ手を持つことを前提としたシミュレーションの場合は形状をより気にしてモデリングした方が良いかもしれません.

モデル形状の完成

Gazebo/MoveIt のための 3D モデリング(6)滑らかなサーフェス – 作成編(その1)」で作成した洗濯機前面上部のボタン類も現在の作業レイヤーにコピーしてソリッドの「和(BooleanUnion)」をします.

また洗濯機前面上部のディスプレイ部境界線を YZ平面上 に描画して洗濯機前面サーフェスに「投影(Project)」してその曲線を用いて洗濯機前面サーフェスを「分割(Split)」します.

洗濯機本体側洗濯槽扉周辺の色違いで別部品となっている部分も正面図から大きさを拾ってサーフェスを「分割(Split)」します.

以上で洗濯機全体の形状が完成しました.

色分け前ですが表示形式をいろいろ変えて描画した様子が次のアニメーション画像です.

今回の記事はここまでです.


本シリーズ次回の記事は

「Gazebo/MoveIt のための 3D モデリング(11)形状データのエクスポート」

として,CAD( 本記事では Rhinoceros )の形状データを Gazebo や MoveIt で利用できるデータ形式にエクスポートする手順などを中心に紹介する予定です.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(9)滑らかなサーフェス – 作成編(その4)

前回は「Gazebo/MoveIt のための 3D モデリング(8)滑らかなサーフェス – 作成編(その3)」として NURBS サーフェスの制御点や次数を編集する作成・調整方法について紹介をしました.

今回は制御点の大幅な位置調整も含めた NURBS サーフェスの調整をして残りの暫定オープンエッジ周辺のサーフェスを作成します.また洗濯機の洗剤投入部を開けるために指を入れる凹み形状も制御点の移動での作成例も紹介します.

今回のゴールは左の図の状態です.

洗濯機前面・側面・上面間コーナ部のサーフェス

左の図に赤色で示している洗濯機前面・側面・上面間コーナ部のサーフェスを作成します.

編集対象となる「サーフェスを抽出(ExtractSrf)」します.

新たに作成するサーフェスの1辺の形状を表す曲線を作成したいので,その基礎形状とするためにまず洗濯機前面・上面間のフィレットサーフェスの「アイソカーブを抽出(ExtractIsocurve)」します.

抽出したアイソカーブを洗濯機側面と平行な平面上に「投影(Project)」して基礎形状曲線として利用する配置にします.

投影する際に投影方向を「カスタム」にして始点をアイソカーブの上端点,終点をフィレットサーフェスの上端点に指定します.

結果的に洗濯機前面・上面間のフィレットサーフェスのトリムされた端部形状とほぼ同じ曲線になります.それでは最初からそのトリムラインを「複製(DupEdge)」すれば良いのでは? となるかと思いますが,曲面をトリムした端部形状は 多点3次 の NURBS 曲線になってしまうので滑らかな曲線にならずそれを基に作成するサーフェスもなめらかになりません.曲面の制御点・次数がそのまま反映されるアイソカーブを「平面」に投影することで 8点7次 のままの NURBS 曲線を作成するサーフェスの基礎形状としたいのでこの手順を採用しています.

投影した基礎形状とする曲線から「点を抽出(ExtractPt)」と「グループ化(Group)」を行い,新たに作成するサーフェスの辺形状曲線の基となる制御点の参考点とします.

トリムのための平面や洗濯機側面と新たに作成するサーフェス間の曲線形状端の配置のための直線を準備します.

以前の記事で洗濯機前面・上面間のフィレットサーフェスの高さは 30mm として作成しました.それと大体同じ大きさのフィレット曲線で洗濯機側面の平面サーフェスを再トリムして,その部分を新しく作成するサーフェスの1辺とします.そのために先程,洗濯機側面平面の上前頂点から水平に描画した直線を下方(Z軸のマイナス方向)に 30mm 移動させます.

先程抽出した制御点参考点グループを直線移動でできたた交点にコピーします.

サーフェスをトリムするためのサーフェスを作成するためにコピー前とコピー後の制御点参考点グループの対応する両端点間に「直線(Line)」を描画します.

トリムするためのサーフェスを「曲線を押し出し > 曲線に沿って(ExtrudeCrvAlongCrv)」で作成します.

トリムのための交差部が確実に算出されるように「サーフェス > 延長(ExtendSrf)」でトリムするためのサーフェスを拡大します.

角部サーフェスをトリムします.

洗濯機側面上の新しいトリム曲線を「曲線をブレンド(BlendCrv)」でコピーしていた制御点の参考点に合わせて作成します.

もう一方のレール曲線についても制御点の参考点をコピーして「曲線をブレンド(BlendCrv)」でそれに合わせて作成します.

またサーフェスをトリムしたトリムラインは 多点3次 の NURBS 曲線になってしまっているので,それに近しい形状の曲線としてサーフェスのアイソカーブをカスタム方向で平面のトリムサーフェスに「投影(Project)」してアイソカーブと同じ次数・制御点の NURBS カーブをサーフェス作成に利用します.

作成したいサーフェス周りの4辺の曲線が準備できましたので「2レールスイープ(Sweep2)」でサーフェスを作成します.4辺全て曲線から作成しますので接続条件は「位置」連続しか選択できませんが,サーフェスを作成後に「マッチング(MatchSrf)」などで曲率や曲率変化率を周辺サーフェスに合わせる手順で行います.

作成したサーフェスの「マッチング(MatchSrf)」を行います.まずは次の図に示す2辺を「アイソカーブ方向の調整」の設定を「ターゲットアイソカーブの方向をマッチング(M)」にして「マッチング(MatchSrf)」を実行します.結果を得てサーフェスを周辺サーフェスと「結合(Join)」して「エッジを表示(ShowEdges)」するとマッチングを行った辺が接続されています.

次に洗濯機側面の平面に接続する辺を「連続性」の設定を「位置」にして「マッチング(MatchSrf)」を実行します.位置連続性でのマッチングの場合はその辺から1つ目の制御点のみ調整されます.結果を得てサーフェスを周辺サーフェスと「結合(Join)」して「エッジを表示(ShowEdges)」するとマッチングを行った辺も接続されています.

ここは主に現状の制御点数で洗濯機側面平面のトリム部と接続が可能かの確認を目的としています.今回は現状接続先のある3辺で接続可能なことが確認できましたので後は辺から2つ目以降の制御点の調整をして接線方向,曲率や曲率変化率の連続性を調整すれば良いことがわかりました.

続いてサーフェス3辺の曲率連続条件での接続を調整します.「アイソカーブ方向の調整」の設定を「アイソカーブの方向を維持(P)」にして「マッチング(MatchSrf)」を実行します.Rhinoceros では「アイソカーブ方向の調整」が複数辺マッチング時に各辺別の設定ができないので各辺ごとの「アイソカーブ方向の調整」を設定したい場合は何度か設定を変えながら「マッチング(MatchSrf)」を繰り返してサーフェスの連続性を調整する必要があります.

「ゼブラ(Zebra)」でサーフェスの接続状況を確認すると少しずれている箇所が見受けられるので制御点を直接的に調整します.

洗濯機側面に接続する辺から数えて4番目の制御点までは他の2辺が接続するサーフェスも洗濯機側面の平面サーフェスに対して曲率変化率連続で接続しているので Y方向 の座標を洗濯機側面平面に合わせると大体合うはずです.

これらの制御点に対して変形の「XYZを設定(SetPt)」でワールド座標系の Y座標 をセットします.

曲率連続以上の滑らかさを求めている場合は制御点の第3点以内を編集したら再度「マッチング(MatchSrf)」を曲率連続条件にて実行して,「エッジを表示(ShowEdges)」や「曲率表示(CurvatureGraph)」などで確認しながら進めます.

制御点の第3点までの配置できたら(曲率連続),制御点の第4点を調整します.第4点各点を第1〜3点の延長上に配置すると「曲率変化率連続」に近いサーフェスになります.

第4点の配置の基本的な手順は接続先のサーフェスの曲率からくる第1〜3点までの状況によりいくつかあり,またそれらの組み合わせるなどして曲率変化率連続になるように配置します.

  • 曲率変化率連続のための第4制御点配置手順の例
    • 第1〜3点までの座標が XYZ のどれかで同値ならば「XYZを設定(SetPt)」で座標を指定して配置
    • 接続先のサーフェスが曲率ゼロの平面なら第1〜3点の延長直線上に第4点をガムボールなどで移動して配置
    • 接続先のサーフェスが曲率一定なら第1〜3点の延長円弧上付近に第4点をガムボールなどで移動して配置
    • 接続先のサーフェスの曲率が変化する場合は「曲率表示(CurvatureGraph)」をして接続先の曲率変化に合うように第4点を移動配置
    • UV 方向で共有する制御点第4点は両方向の都合が合う位置に移動配置
少し余談ですが,このように曲率変化率連続に調整するためには第1〜3の制御点とそれぞれの第4制御点を比較できる視点で第4制御点をガムボール移動する必要が多くなるので, 3D マウスがあると視点を移動しやすく作業効率が良くなると思います.

サーフェスの「マッチング(MatchSrf)」や制御点の「XYZを設定(SetPt)」,ガムボール移動などを繰り返して曲率変化率連続になるようにサーフェスを修正します.

おおよそ曲率変化率連続に近づいた状態でここで編集しているサーフェスとその周辺のサーフェスを「ゼブラマッピング(Zebra)」して縞の方向を縦と横にしたものが次の図です.だいぶゼブラが滑らかに通るようになったのではないかと思います.

残りのフレット部分のサーフェスを作成します.

先程と同様にアイソカーブをトリム平面にカスタム方向にて「投影(Project)」してフィレットサーフェス両端のプロファイル曲線とします.

色々と試しているときに洗濯機前面と側面間のフィレットサーフェスの長さが足りなかったので「サーフェスを抽出(ExtractSrf)」,「トリム解除(Untrim)」してから「サーフェス延長(ExtendSrf)」(タイプ: スムーズ)を行って,再度トリム平面で「トリム(Trim)」しました.

フィレットサーフェスを「2レールスイープ(Sweep2)」で作成します.レール曲線にサーフェス端部形状を使い,断面曲線に先程アイソカーブをトリム平面に「投影(Project)」した曲線を使います.後で「マッチング(MatchSrf)」で曲率連続での接続に修正するので,今回の作成時は接続条件は位置のみにします.

作成したフィレットサーフェスの制御点を「制御点表示オン(POn)」で表示してみると V方向 は 8点 ですが U方向 は多くの点で構成されていることが分かります.また次数は V方向 は投影したアイソカーブが反映されていて 7次 で U方向 はレール曲線の1つがサーフェス断面なので 3次 です.

U方向 の制御点数がサーフェス内での滑らかさの調整や周辺サーフェスとの曲率変化率連続のために第4点を全て手動で調整するには多いように思いますので「RebuildUV(メニューにはない)」で “U方向のみ” サーフェスを再構築します.

今回は再構築後の U方向の 制御点数を 11個 にしています.また「RebuildUV」オプションで「タイプ(T)=ユニフォーム」にしたので再構築後の次数は 3次 になります.

Rebuild と RebuildUV
「RebuildUV」は “UもしくはVの1方向” のみですが,「サーフェスをリビルド(Rebuild)」は “UV両方向” についてサーフェスを再構築しますので適宜使い分けます.

  • Rebuild
    • UV両方向を再構築
    • 再構築後の次数は制御点数より少ない数で任意に設定可能
    • 制御点数と次数が再構築前と同じでも再構築により少し形状が変化
  • RebuildUV
    • UもしくはVの1方向のみを再構築
    • 再構築後の次数は最大でオプション「タイプ(T)=ユニフォーム」にて 3次
    • 再構築を行わない方向の制御点数および次数は元のままで比較的再構築前の制御点配置がそのまま反映

再構築したフィレットサーフェスの U方向 の次数は 3次 なので最終的には「次数を変更(ChangeDegree)」で 7次 にします.「次数を変更(ChangeDegree)」で 3次 から 7次 にする場合,次数を変更する方向の制御点に新たに4つ(セット)が元のサーフェスに対して増えます.

「次数を変更(ChangeDegree)」で 7次 に変更して制御点が増える前にまず 3次 の時点で制御点の配置を整えておきます.

洗濯機上面と側面間のフィレットおよび前面と側面間のフィレットはそのプロファイル曲線の制御点の Y座標 をほぼ一致するようにしていましたので両フィレットサーフェス間にある今編集しているサーフェスの制御点の Y座標 もそれらと大体一致するものと考えられます.そこで「制御点を選択 > U方向をすべて選択(SelU)」で8列の1つずつ U方向 の制御点を選択して「XYZを設定(SetPt)」でワールド座標系の Y座標 の設定にて各端点を選択して Y座標 位置を整えます.

U方向 だけ次数が 3次 のうちに一度曲率連続で接続先サーフェスに「マッチング(MatchSrf)」を「ターゲットアイソカーブの方向をマッチング(M)」で行い接続します.

フィレットサーフェスの U方向 の次数を 「次数を変更(ChangeDegree)」で 3次 から 7次 に変更します.

次数が 7次 に変更された U方向 だけ曲率連続で接続先サーフェスに「マッチング(MatchSrf)」を「ターゲットアイソカーブの方向をマッチング(M)」で行い接続します.

今度は4辺全てにおいて曲率連続で接続先サーフェスに「マッチング(MatchSrf)」を「アイソカーブの方向を維持(P)」で行い接続します.

フィレットサーフェスの4辺が曲率連続で接続されているはずですので周辺のサーフェスと全て「結合(Join)」してから「エッジを表示(ShowEdges)」で隙間が無いことを確認し.また「曲率分析(CurvatureAnalysis)」でおおよその曲率連続性を見てみます.

今回のフィレットサーフェスは U方向: 15点7次,V方向: 8点7次 で4辺とも周辺のサーフェスと隙間なく接続することができました.
しかしこのようなフィレットサーフェス作成・修正時には曲率の大きい接続部周辺で制御点が足らずに形状が精度内で一致せずに隙間が出来てしまうことが頻繁にあります.このような場合には次の手順を(繰り返し)行うと隙間なく接続されるようになります.

  1. 曲率の大きな接続部周辺に「ノットを追加(InsertKnot)」を実行(形状追従性を向上)
  2. 全辺に対して再度サーフェスの「マッチング(MatchSrf)」を実行
  3. 隙間の有無を「エッジを表示(ShowEdges)」で確認 → あれば 1. から再度実行

大体曲率連続接続になっているので後は「曲率変化率連続」になるように全ての第4制御点について第1〜3制御点に対する位置の調整を行います.

第4制御点の位置調整をする際にガムボールの白い丸をクリックするとガムボール設定が表示されるので適宜ガムボールの移動方向を「ワールドに合わせる」や「ビューに合わせる」に変更したり,「ドラッグ強度」を小さく設定して微妙な位置修正をできるようにするとより良く編集できると思います.

全ての第4制御点の位置を大体調整して「曲率表示オン(CurvatureGraph)」を行ったのが右の図です.おおよそ曲率変化率連続(G3)に近い状態になっているかと思います.

「ゼブラ(Zebra)」表示をして「縞の方向」と「縞のサイズ」を変更したものが次の図です.ゼブラも比較的良く通っているように見えます.

また,作成したポリサーフェスを「環境マッピング(Emap)」表示を行ってもサーフェスやその接続の滑らかさの様子を見ることができます.

これで洗濯機本体の隙間があった部分のサーフェスもできましたので今回作成・編集したサーフェスで置き換えた洗濯機本体のポリサーフェスを再構成して「結合(Join)」したものが右の図です.

洗剤トレイ付近形状 – 制御点移動による曲面の作成

今回の記事の最後に平面に接続する場合において曲率変化率連続接続されるサーフェスのパワープレー気味ですが簡単な制御点移動による曲面の作成方法を1つ紹介します.

本項で作成するのは左の図の赤い楕円で囲ったところにある洗濯機の洗剤投入トレイに指を掛けるためのトレイ後ろ側本体窪み形状のサーフェスです.

洗剤トレイとそれが入る洗濯機本体部分のモデリング手順はここでは省略して既にあるものとします.

上面図に少しサーフェス範囲の線が入っているので洗濯機上面平面前端曲線の同心円やトレイの中心線からのオフセットラインを描画してサーフェスの作成範囲の参照線とします.

新たに作成するサーフェスの1辺として同心円間に円の中心からサーフェス端の方向に「直線(Line)」を描画します.

描画した直線を同心円中心周りに「回転(Revolve)」でサーフェスを作成します.

この「回転(Revolve)」で作成されたサーフェスは円弧方向は 3点2次,直径方向は 2点1次 で構成されたサーフェスになります.

このサーフェスを変形して曲面にしても洗濯機上面の平面に曲率変化率連続で接続するには洗濯機上面につながるサーフェスの辺から第4の制御点までこの平面上に位置していれば良いので「リビルド(Rebuild)」して制御点と次数を変更して再構築します.

リビルド(Rebuild)」で U方向 を 16点7次 に V方向 を 8点7次 にします.

洗濯機上面の平面に接続する3辺から第4までの制御点以外の制御点を選択してガムボールで Z軸方向 に -15mm 移動させます.

「曲率表示オン(CurvatureGraph)」をすると平面に接続する3辺が曲率ゼロ,曲率変化率ゼロになっていることが見て取れます.

このように NURBS サーフェスの接続連続性と制御点と次数の関係が分かっていると単純な操作で滑らかに接続するサーフェスを作成できるケースもありますので便利です.

以前の記事も含めてこれまで紹介してきたようにサーフェスの作成方法は様々ありますが,最終的には適切な次数で「制御点群をどう配置して意図するサーフェスを作るか」という点が NURBS サーフェスでは大事な点となります.

今回作成したサーフェスを含めた洗濯機モデルの全体像は右の図のようになります.

今回の記事はここまでです.


「滑らかなサーフェス – 作成編」などサーフェスの作成方法に主眼を置いたモデリングの記事は今回で終了となります.

本シリーズ次回の記事は

「Gazebo/MoveIt のための 3D モデリング(10)部品作成編」

として,洗濯機の扉などの部品の作成例を紹介する予定です.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(8)滑らかなサーフェス – 作成編(その3)

前回は「Gazebo/MoveIt のための 3D モデリング(7)滑らかなサーフェス – 作成編(その2)」としてサーフェスのプロファイル曲線の制御点や次数を少し意識した滑らかなサーフェスの作成例を紹介しました.

今回は NURBS サーフェスの制御点や次数を編集する作成・調整方法について紹介しつつ,前回の記事中では暫定的にオープンエッジのままとしていた箇所,左の図で赤く示した辺りのサーフェスを作成します.

編集対象となる「サーフェスを抽出(ExtractSrf)」してから洗濯機のソリッドモデルを非表示にしておきます.

前面の上下を接続する大きめのフィレットサーフェスに合わせて新しいサーフェスを作成したいので,既存のフィレットサーフェスを「分割(Split)」して不要部分を削除します.

この際,コマンド内設定で「切断に用いるオブジェクトを選択」に「アイソカーブ(I)」を指定し,洗濯機前面の上下を接続する大きめのフィレットサーフェスの端点にアイソカーブを合わせて分割します.

アイソカーブ(Isocurve)
「アイソカーブ」はサーフェスの UV 各方向に沿った曲線で,サーフェス上の1点における各方向の制御点配置と次数がそのまま反映されます.アイソカーブではないサーフェス上の曲線や分割線は Rhinoceros では 多点3次 の曲線として扱われるため,3次 よりも大きな次数の NURBS サーフェスの場合はそのアイソカーブに比べてアイソカーブではないサーフェス上の曲線は滑らかではない可能性が高いです.よって,サーフェスを分割する場合は可能であればアイソカーブで分割すると,その分割部に接続するサーフェスを作成する際に滑らかなサーフェスを作成しやすくなります.

フィレットサーフェスに隣接する角部の大きい方のサーフェスも同様にアイソカーブで「分割(Split)」します.

新規に作成するサーフェスを既存の洗濯機前面上下間サーフェスに合うものにしたいのでその既存のプロファイル曲線の制御点をサーフェスの1辺の曲線を作成するための参考点とします.

洗濯機前面上下間サーフェスの中心の「アイソカーブを抽出(ExtractIsocurve)」し,抽出したアイソカーブに対して「点を抽出(ExtractPt)」を行い,扱いやすいように「グループ化(Group)」します.

抽出した制御点のグループをブレンド曲線の参考点とするために「配置 > 2点指定(Orient)」で「コピー」を「はい」,「スケール」を「3D」に設定することで作成するサーフェスのレール曲線を作成する部分の各両端にスケールコピーします.

「曲線ブレンド(調整)(BlendCrv)」で各制御点をコピーした点群にスナップすることで曲線を作成します.曲線を作成できたら点群は非表示にするか削除しておきます.

洗濯機側面の平面も暫定的なトリムラインになっているので「境界曲線を複製(DupBorder)」してから「すべてトリム解除(UntrimAll)」を一度して,境界曲線の一部を作成したレール曲線に置き換えて再度「トリム(Trim)」します.

ここの新しいサーフェスは周辺の面構成から判断するに「レールに沿って回転(RailRevolve)」で作成するのが良さそうです.

「レールに沿って回転(RailRevolve)」でのサーフェス作成に必要なデータは「プロファイル曲線」「レール曲線」「回転軸」で,前者2つは既にあり,「回転軸」の方向は Y軸と平行 にするので回転軸の「中心点」1つを準備します.

準備として下の図のように,解析ツールの「半径(Radius)」のコマンド内オプションで「半径カーブを作成(M)=はい」にすると曲率半径に対応した円を作成してくれるのでそれを利用します.そして作成した円の中心点と作成するサーフェスの端点を結んだ線を2つ描画してその交点を「レールに沿って回転(RailRevolve)」の回転軸の1つの「中心点」として軸方向はその中心点から Y方向の1点 を [ Shift ] キーで指定することにします.

準備が整いましたので「レールに沿って回転(RailRevolve)」でサーフェスを作成します.

「輪郭曲線」(プロファイル曲線)と「レール曲線」回転軸の「始点」(交点)と「終点」(交点からY軸と並行方向の1点)を指定します.

「レールに沿って回転」で作成したサーフェスは周辺のサーフェスとの接続および接続条件が確約されたものではないので,作成したサーフェスのエッジで接続先サーフェスがある3辺について接続条件のマッチングを行います.

サーフェスの「マッチング(MatchSrf)」を実行し,コマンド内オプション「複数マッチング(M)」として「m」を設定してから3辺それぞれにおいて「接続させたいエッジ」とその「接続先のサーフェスエッジ」の選択を行います.

( 都合 6回 選択クリック = 3辺 ✕( 接続させるエッジ + 接続先のサーフェスエッジ ) )

  • 連続性: 曲率
  • 一番近い点でエッジをマッチング
  • アイソカーブの方向を維持

サーフェスのマッチングを行ったら他のサーフェスと「結合(Join)」して,まずは「エッジを表示(ShowEdges)」で隙間が無い(=位置連続)ことを確認します.

「マッチング(MatchSrf)」では「曲率連続」までしか合わせないので「曲率変化率連続」については各種「解析」ツールを利用して目視でチェックします.

「ゼブラマッピング(Zebra)」は作成したサーフェスと上下のサーフェスや洗濯機側面の平面ともに縞模様が滑らかにつながっていますので曲率連続から曲率変化率連続ぐらいの連続性であろうと思われます.

ポリサーフェスを「分解(Explode)」して作成したサーフェスだけ「曲率表示オン(CurvatureGraph)」させたのが右の図です.

洗濯機側面の平面(曲率ゼロ)に対して V方向 の曲率が変化率も含めて連続しているように見えます.

右の図は作成したサーフェスに対して「制御点表示オン(PointsOn)」をして洗濯機前方(Rhinoceros での Right ビュー)から見た図です.

洗濯機側面の平面からつながる制御点各4点が洗濯機側面平面と同一平面内にあるので,制御点の配置からも曲率ゼロの平面に曲率変化率連続で接続しているであろうことが見て取れます.

ゼブラマッピングを見ていて今回作成したサーフェスとは別のサーフェスの接続連続性があまり良くない箇所を見つけました.

ゼブラのずれは単に「解析メッシュが粗いことで生じてしまう見かけ上のずれ」の場合もあります. その確認は「ゼブラオプション」内の [ メッシュを調整… ] ボタンから「最大エッジ長さ」を短くしてメッシュを細かくしてゆくことで解消されるような場合は「見かけ上のずれ」と考えられます.

ただ,今回はメッシュを細かくしてもゼブラのずれは解消されませんでした.

筆者の経験では「円弧(Arc)」を分割すると,サーフェスの作成経緯からはゼブラはずれないであろう箇所でゼブラのずれが生じることが多く起きるように感じています.

このフィレットサーフェスはこの後作成するフィレットサーフェスにも影響するのでこの時点で修正しておきます.フィレットサーフェスの U方向 である 3点2次 の円弧を必要な部分だけを 8点7次 の NURBS データに変換してからサーフェスの「マッチング(MatchSrf)」することで曲率変化率接続のサーフェスに修正します.

「マッチング(MatchSrf)」で接続する先のサーフェスの端点からのアイソカーブで「分割(Split)」して必要な部分を残します.

「分割(Split)」を行っただけでは基となるサーフェスの一部となっているだけですので,基となるサーフェスをトリム境界線近くまで縮小するために「トリムサーフェスをシュリンク(ShrinkTrimmedSrf)」を実行します.

そして「次数を変更(ChangeDegree)」で縮小したサーフェスの U方向 の次数を 7 に変更し, V方向 についても指示を問われますがこちらも基と同じ 7 として実行します.これにより U方向 の制御点が次数に合わせて 8 (=次数+1)に変わります.

次数の変更を行ったサーフェスに対して「マッチング(MatchSrf)」を実行して2辺を接続先のサーフェスと曲率連続にします.

「制御点表示オン(PointsOn)」と「曲率表示オン(CurvatureGraph)」により曲率連続にしたサーフェスの曲率接続状況を確認します.

曲率変化率連続は「マッチング(MatchSrf)」では調整されないので,目視確認して修正が必要であれば接続端から4番目の制御点を手動で調整します.

今回は元々のサーフェスが曲率変化率連続接続で作成して変更も主に次数のみの小さなものだったので第4の制御点の調整は必要ありませんでした.

このサーフェスの U方向 の制御点は 8点 なので曲率変化率連続の場合は 4番目の制御点 も 1辺あたり8個 で 両辺で16個 ありますのでざっと各16点について確認します.

このように制御点が多くなると調整作業も多くなってしまうのでサーフェスを作成・修正変更する場合に必要十分な少ない制御点のサーフェスとなるようにすると良いです.

修正前と後のゼブラマッピングをアニメーションで比較してみると改善されているようです.

修正したフィレットサーフェスが良さそうなので Z=350mm でミラーリングして下部のフィレットサーフェスも置き換えておきます.

分析メッシュの消去
Rhinoceros 7 の Mac 版ではあまり気にしたことなかったのですが Windows 版の場合に解析で作成されたメッシュが全て保存されるようになっており 3MB ほどのデータが 6GB ほどのファイルとして保存されて上書き保存も難しい状況に陥ってしまいました.この対策として時々「分析メッシュを消去(ClearAnalysisMeshes)」を行うと再度分析するときには時間がかかりますがファイルサイズを小さくすることができます.

洗濯機前面と側面間に穴として残っているフィレットサーフェスも「レールに沿って回転(RailRevolve)」で作成して「マッチング(MatchSrf)」で修正を行います.

今回の記事はここまでです.


本シリーズ次回の記事は

「Gazebo/MoveIt のための 3D モデリング(9)滑らかなサーフェス – 作成編(その4)」

として,暫定的に隙間を開けてしまっている部分の残り,洗濯機前面・上面・側面が合わさるコーナー部を塞ぐサーフェスの作成例を紹介する予定です.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(7)滑らかなサーフェス – 作成編(その2)

前回は「Gazebo/MoveIt のための 3D モデリング(6)滑らかなサーフェス – 作成編(その1)」として Rhinoceros のコマンドを利用した滑らかなサーフェスの作成例を紹介しました.

今回は NURBS の次数と制御点を少し意識したサーフェスの作成方法について説明します.

洗濯機両サイドの角部周辺サーフェス

まず,洗濯機両サイドの角部周辺のサーフェスを作成します.

側面視(Rhinoceros の Back ビュー)に線があるのでこれをサーフェスの1辺とします.このサーフェスの1辺に相当する線を「直線(Line)」で描画します.

また洗濯機側面後部の辺を「エッジを複製(DupEdge)」で複製します.

そして「洗濯機側面後部の辺を複製した曲線」の「サーフェスの1辺に相当する線」よりも高い部分を「トリム(Trim)」します.

ブレンド曲線を「曲線ブレンド(調整) (BlendCrv)」で描画します.

  1. 1つ目の接続先に先程トリムした曲線を選択
    • 接続条件: G3(曲率変化率連続)
  2. 2つ目の接続先を BlendCrv コマンド内の設定で「エッジ(E)」に設定してから洗濯機上面後端のエッジを選択
    • エッジ接続の場合は接続条件は「位置連続」になる
    • 描画時にそのエッジ上で制御点を調整可能

ブレンド曲線の描画中にビューを洗濯機正面(Rhinoceros の Right ビュー)に移動して図面上の洗濯機上部角部形状に沿うように制御点の調整をしてからブレンド曲線の描画の確定をします.

結果, 5点4次 の NURBS 曲線になります.

「曲線を押し出し(ExtrudeCrv)」でサーフェスを作成します.

洗濯機上面と側面間のサーフェス作成で使ったプロファイル曲線をコピー移動して洗濯機前面と側面間のサーフェスも作成します.

洗濯機前面は球面サーフェスですので平断面でトリムすると円になりますのでその円の中心を通る Y軸 と平行な軸回りにプロファイル曲線を「回転(Revolve)」します.

洗濯機前面サーフェスをこれまで作成してきた洗濯機ソリッドモデルから「サーフェスを抽出(ExtractSrf)」して新規サーフェスの作成作業を失敗しても元ソリッドに影響がないようにしておき,また洗濯機ソリッドモデルは「非表示」か「ロック」状態にしておきます.

洗濯機前面サーフェスのトリムをします.洗濯機上面と側面間のサーフェスの上端のエッジを「複製(DupEdge)」した曲線で上面視(Top ビュー)でトリムします.

前述のようにここでトリムされた前面サーフェスのエッジは円弧ですので,洗濯機側面視(Rhinoceros の Back ビュー)で Osnap の「中心点」をチェックして「円(Circle)」を描画します.この円弧エッジをクリックすることで中心点を選択してこの円弧の端点をクリックして半径を決定します.

円を描画したら「回転(Revolve)」でサーフェスを作成するプロファイル曲線の配置が容易なのと洗濯機前面付近のみサーフェスを作成したいので,円の中心から水平に直線を描画して円の下半分をトリムします.

円の前端部にサーフェスのプロファイル曲線を配置します.「コピー(Copy)」して「回転(Rotate)」しても良いですし,前回の記事で利用した「3点指定配置(Orient3Pt)」でコピー配置しても良いです.

「回転(Revolve)」を使ってプロファイル曲線を円の中心を通る Y軸 に並行な軸回りに 0° から洗濯機上面より高い位置までサーフェスが作成するよう角度を指定してサーフェスを作成します.

洗濯機前面下部のサーフェスが上部サーフェスを Z=350mm の面で「ミラー(Mirror)」したものであるのと同様に洗濯機前面と側面間のサーフェスもミラー反転するとともに Z=350mm でトリムして「結合(Join)」します.

前回の記事までで作成した洗濯機ソリッドモデルのサーフェスをトリムしたものに置き換えるなどして今回新しく作成したサーフェスと「結合(Join)」したポリサーフェスが右の図です.

結合したポリサーフェスでは「エッジを表示(ShowEdges)」で洗濯機前面・側面・上面の交わる頂点付近に隙間が確認できますが,この箇所は後でフィレットサーフェスを作成して閉じるので,ここでは暫定的に各辺の交点を結んだ直線で洗濯機側面視で両サーフェスをトリムしています.

互いが同じサーフェスに接続する曲率変化率連続のサーフェス同士の場合,サーフェスの交差角度が浅すぎて交わる曲線を Rhinoceros が計算できないことがあります.サーフェスを細分しながら交線を計算させたり,計算できた範囲の交線と大体それらしい曲線を手動で描画したりして閉じたポリサーフェスにすることも可能ですが,今回は最終的には使わないトリムラインですので暫定的にオープンエッジにしています.

曲率変化率連続フィレットサーフェス

前回の記事の四角いボタンのモデリングでは「曲率連続フィレットサーフェス」を作成し,また丸いボタンの角部は「曲率変化率連続曲線」を「回転(Revolve)」して「曲率変化率連続のフィレットサーフェス」を作成しました.

ここでは前回の丸いボタンの方法の応用として「曲率変化率連続曲線」を「曲線を押し出し(ExtrudeCrv)」し,また「制御点を編集した曲率変化率連続曲線」を「回転(Revolve)」して「曲率変化率連続フィレットサーフェス」を作成します.

まずは洗濯機上面と側面コーナー部の間に「曲率変化率連続のフィレットサーフェス」を作成します.先述のコーナー部の大きめのサーフェスと同じように曲率変化率連続のプロファイル曲線を作成して「曲線を押し出し(ExtrudeCrv)」でサーフェスにします.

プロファイル曲線は洗濯機の上面図にフィレットの長辺の線が見られるのでそれを目安にして両端の接続条件「G3(曲率変化率連続)」にて 8点7次 の曲線を描画します.

プロファイル曲線を「曲線を押し出し(ExtrudeCrv)」してフィレットサーフェスを作成します.

次に洗濯機前面と側面コーナー部の間に「曲率変化率連続のフィレットサーフェス」を作成します.これもコーナー部の大きめのサーフェスと同じように曲率変化率連続のプロファイル曲線を作成して「回転(Revolve)」でサーフェスにします.

先程の “洗濯機上面と側面コーナー部の間に「曲率変化率連続のフィレットサーフェス」” のプロファイル曲線をコピー配置します.

洗濯機前面の球面サーフェスに曲率変化率連続で接続するので曲線の修正が必要で,次の図にあるような側面コーナー部形状と洗濯機前面の形状の両方に曲線変化率連続接続するプロファイル曲線にします.

また,修正したプロファイル曲線が元のプロファイル曲線と同じような制御点配置をしていると後の記事で取り上げる両フィレットサーフェス間をつなぐフィレットサーフェスを作成するときに便利なので制御点の Y方向位置 を揃えるようにします.

制御点の Y方向位置 を揃えるための補助線を描画しておきます.

  1. コピー配置したプロファイル曲線を選択後「点を抽出(ExtractPt)」にて制御点を描画
  2. 抽出した制御点群をまとめるために「グループ化(Group / Ctrl-G)」
  3. Y座標の補助線として洗濯機前面形状側から第2,3,4点目から「直線(Line)」を X軸方向 に描画
  4. 描画した3直線をまとめるために「グループ化(Group / Ctrl-G)」

プロファイル曲線の修正の方法は主に2つの方法「再構築的方法」と「修正+調整的方法」があるのではないかと考えています.

8点7次 の曲線のようなブレンド曲線(BlendCrv)の機能に備わっているのと同じ曲線の場合は「再構築的方法」を用いて,曲線の次数+1個よりも多い制御点を持つようなブレンド曲線(BlendCrv)の機能に備わっていない曲線の場合は「修正+調整的方法」を用いると比較的整った曲線ができるのではないかと思います.

  • 再構築的方法
    1. 「曲線ブレンド(調整)(BlendCrv)」で修正元の曲線と同じ接続条件を選択
      (今回は両端とも G3 接続)
    2. 「曲線ブレンド」で描画中に制御点を修正元の曲線の制御点(の抽出点)や
      補助線上に合わせたら描画確定
  • 修正+調整的方法
    1. 曲線を選択して「マッチング(Match)」コマンドを開始して接続先曲線の端点近くをピック
    2. 連続性を「曲率」に設定してマッチングを確定実行
      (Rhinoceros のマッチングの機能として「曲率連続」までしかない)
    3. 修正している曲線の制御点を表示(P-On)と曲率を表示(CurvatureGraph)にする
    4. 曲率変化率連続にしたい場合は曲率表示のグラフを見ながら接続先側から第4の点をガムボールで位置調整
    5. 第2,第3の点も補助線上に来るように調整した場合はそれぞれ接線連続性と曲率連続性も崩れるので本リストの 1. に戻って良好な曲線が得られるまで繰り返す

「修正+調整的方法」はサーフェスのマッチング修正でも同様のことを3次元的に行います.これは次回の記事で紹介する予定ですので,予習的に今回の2次元上での調整方法を体験しておくのも良いかもしれません.

プロファイル曲線ができてしまえば後は洗濯機の上面と側面のコーナー部サーフェスと同じ軸回りの「回転(Revolve)」と「ミラー(Mirror)」を実行して適切にトリム・結合するとフィレットサーフェス作成は終了です.

ここでも暫定的にオープンエッジを残しています.

一部のサーフェスを曲率分析(CurvatureAnalysis)したときのキャプチャ画像が次の図です.

洗濯機前面フィレットサーフェス

洗濯機の前面と上面の間の少し大きめのフィレットサーフェスと洗濯機前面の上下のミラー反転されているサーフェス間の大きめのフィレットサーフェスもこれまで紹介してきた方法と同様に曲率変化率連続のサーフェスとして作成しますので,大まかに紹介します.

フィレットを架けるサーフェスを洗濯機のポリサーフェスから抽出して編集します.

  • 洗濯機の前面と上面の間のサーフェス
    1. 上面図にあるフィレットのエッジと思われる曲線などから判断して,
      上面から 30mm 低い高さを洗濯機前面側のエッジとする
    2. 洗濯機中央(XZ平面)上にプロファイル曲線を側面図と照らし合わせながら
      曲率変化率(G3)連続で描画
    3. 「回転(Revolve)」でサーフェスを作成
  • 洗濯機前面の上下のミラー反転されているサーフェス間のサーフェス
    1. 上下ミラーした高さ Z=350mm から ±50mm の範囲の前面サーフェスをトリム
    2. 同様に洗濯機中央(XZ平面)上にプロファイル曲線を側面図と照らし合わせながら曲率変化率(G3)連続で描画
    3. 「回転(Revolve)」でサーフェスを作成

右の図は洗濯機両サイドのフィレットエッジの Y座標 で新規作成した2つのフィレットサーフェスをトリムして結合したものです.

洗濯機全体のポリサーフェスのうち洗濯機前面と上面のサーフェスをフィレットを追加したポリサーフェスで置き換えて結合して「エッジを表示(ShowEdges)」したのが右の図です.

まだ暫定的に隙間を開けたままにしています.

さらにレンダリング表示をしてキャプチャした画像が右の図です.

だいぶ洗濯機本体の細かいサーフェスも作成できてきたように思います.

今回の記事はここまでです.


本シリーズ次回の記事は

「Gazebo/MoveIt のための 3D モデリング(8)滑らかなサーフェス – 作成編(その3)」

として,今回は暫定的に隙間を開けてしまっている部分を塞ぐサーフェスの作成をする過程で

  • NURBS サーフェスの制御点や次数を直接的に編集するようなケース

について説明する予定です.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(6)滑らかなサーフェス – 作成編(その1)

前回 「Gazebo/MoveIt のための 3D モデリング(5)滑らかなサーフェス – 知識編」 では 3D モデリングにおける滑らかなサーフェスとは何かについて曲率などの連続性や CAD やサーフェスモデラ内での形状表現のされ方について説明しました.

今回は前回の記事の知識を踏まえて滑らかなサーフェスの作成例について説明します.

滑らかなサーフェスは Gazebo や MoveIt のモデルを作成する場合においてはあまり重要性は高くない… と前回の記事を書いた時点では考えていましたが,今後,Gazebo や MoveIt を走らせる PC の CPU / GPU 性能が向上して細密メッシュモデルを使った精密なシミュレーションへの要求も段々と高くなるかもしれない,とも思いました.

洗濯機前面上部の操作ボタン

サーフェスはモデル内で大きいものから順に作成していった方が全体のバランスを確認しながら作業を進められて修正作業が必要になる量も少なくなるので良いと思います.

ただ,本記事ではまず,Rhinoceros に備わっているコマンド1つほどであまり制御点とか次数とか細かく意識しなくても作成できる曲率連続の滑らかなサーフェスの例として小さいサーフェスの洗濯機前面上部の操作ボタンのモデリングについて説明します.

四角いボタンのモデリング

洗濯機の前面視( Rhinoceros の Right ビュー )で四角いボタンの大きさを見ると大体 20mm 角でした.

ボタンを1つモデリングして,それを洗濯機前面のサーフェス上に8つコピーして配置します.

左の図は四角いボタンのソリッドモデルの作成過程をアニメーション化した画像です.

各手順を以下に順を追って説明します.

20mm の正方形の線を選択して「テーパで平面曲線を押し出し(ExtrudeCrvTapered)」 を実行し,ドラフト角度 15° の設定で 3mm 押し出します.

  • テーパで平面曲線を押し出し
    • メニュー: ソリッド(O) > 平面曲線を押し出し(X) > テーパ(T)
    • コマンド: ExtrudeCrvTapered

四隅を「曲率連続」で丸めるために「エッジをブレンド(BlendEdge)」を実行して四隅のエッジを選択します.

デフォルトでは半径が 1mm となっていてモデリングしたい四隅の半径はもう少し大きいので洗濯機前面図と比較しながら寸法を調整します.

  • エッジをブレンド
    • メニュー: ソリッド(O) > エッジをフィレット(F) > エッジをブレンド(B)
    • コマンド: BlendEdge

「エッジをブレンド」コマンド内の設定を次のようにして

  • ハンドルを連動(L)=はい
  • レールタイプ(R)=レール間の距離(I)

次にコマンド内設定で「全てを設定(T)」で半径(上記設定の場合はレール間の距離)を 8mm にしてコマンドを確定します.

「エッジをブレンド」コマンド内の「レールタイプ」の設定は「レール間の距離」にした場合が比較的綺麗なフィレットがかかるように思っているので設定しました.フィレットをかけるサーフェスの組み合わせによって他の設定の方が良い場合もあるので各モデリング対象にて適宜選択してください.

今度は指で押す面の周囲にブレンドエッジを作成します.「エッジをブレンド(BlendEdge)」コマンドを実行してコマンド内設定 「次の半径(R)」 を 2mm に設定してから該当するエッジをすべて選択して確定・実行します.

下の図は「エッジのブレンド」でフィレットを作成した四角いボタンの平均曲率解析とゼブラ解析の表示結果です.

これらのブレンドエッジは数ミリと小さいので問題にはあまりなりませんが,大きなサーフェスとしてブレンドエッジを作成する場合は応用的に次のリストの項目を調整編集すると良いでしょう.

  • ブレンドエッジ関連で応用的な項目
      • BlendEdge だとフィレット接続する方向は 5次-6点 で「曲率連続」になるが,長手方向は 3次-多点 になってしまいガタつく
        • →「次数を変更(ChangeDegree)」で長手方向の次数を 7次 に変更してサーフェスマッチングを行い再接続する
        • → 制御点が多すぎる場合は RebuildUV コマンドで U方向 のみ制御点を少なくしてリビルドして(この時点ではまだ 3次-多点 ),その後で「次数を変更(ChangeDegree)」で U方向を 7次 に変更してサーフェスマッチング(MatchSurf)
      • テーパ押し出しの基となった正方形も各辺直線ではなく少し外に膨らむ緩い円弧にするとより滑らかな感じになる

四角いボタンのモデルができましたので洗濯機前面のサーフェス上にコピーして配置します.

ボタンの押される方向の軸と洗濯機前面のサーフェスの法線軸を合わせ,かつ上下辺を水平に配置したいので「配置(3点指定)・(Orient3Pt)」を利用してコピーします.「 配置(3点指定)」は下記リストの 3点 を移動元と移動先で指定して配置変換するものです.

  1. 移動原点
  2. 移動原点から第1の方向を決める方向上の点
  3. 移動原点と第1方向点で構成される軸回りの方向を決める点

四角いボタンの移動先とするために配置するサーフェス上の法線方向と接線方向の直線を各配置点で描画します.まず,YZ 平面上に四角いボタンを配置する中心線を描画して,洗濯機前面視(Rinoceros の Right ビュー)にてそれらをサーフェスに「投影(Project)」します.

  • 投影
    • メニュー: 曲線(C) > オブジェクトから曲線を作成(F) > 投影(P)
    • コマンド: Project

「直線(Line)」を実行してコマンド内で「法線(N)」を指定,もしくはメニューから「サーフェス法線(U)」を実行して,サーフェスを選択後,サーフェス上にある投影した中心曲線の交点を選択して法線を描画します.

今回の「配置(3点指定)・(Orient3Pt)」ではスケーリングコピーはしないので長さは適当で大丈夫です.順次各点における法線を描画して合計8軸を準備します.

  • サーフェス法線
    • メニュー: 曲線(C) > 直線(L) > サーフェス法線(U)
    • コマンド: Line → 法線(N) を選択

洗濯機前面の球面サーフェスに食い込む形で四角いボタンを配置したいのでコピー元となる点を 1mm ボタンの高さ方向へ移動しておいてから「配置(3点指定)・(Orient3Pt)」でコピー元,コピー先の各3点を指定してボタンをサーフェス上に配置します.

  • 配置(3点指定)
    • メニュー: 変形(T) > 配置(O) > 3点指定(3)
    • コマンド: Orient3Pt

コピー元の3点指定
コピー先サーフェス上のの3点指定

8つの四角いボタンを洗濯機前面のサーフェス上に配置してゴースト表示とレンダリング表示をしてそれぞれキャプチャしたものが次の2つの画像です.

丸いボタンのモデリング

丸いボタンの直径は大体 50mm ぐらいのようです.

丸いボタンのように軸回転形状のものは回転形状のプロファイル曲線を作成してそれを軸回りに回転してソリッドモデル(閉じたポリサーフェス)とするのが一番簡単だと思います.

丸いボタンモデルの作成自体は上記四角いボタンのように作成しやすい場所で作成して,軸回りの形状は軸対称で同じなのでそれを今度は「配置(2点指定)・(Orient)」でコピー移動して洗濯機上のサーフェスに配置します.

丸いボタンのモデリングと配置の作業手順をまとめると次のようになります.

  1. 回転中心軸を含む平面上に回転形状プロファイル曲線を描画
  2. 回転中心軸回りに「回転(Revolve)」にて1周分のサーフェスを作成
  3. 作成されたサーフェス群を「結合(Join)」してソリッド(閉じたポリサーフェス)にする
  4. 丸ボタンモデルのコピー元原点とコピー先の原点と方向(サーフェス法線方向)を描画
  5. 「配置(2点指定)・(Orient)」でコピー移動

回転方向は曲率一定になるので回転プロファイルの曲線さえ滑らかな曲線を作成すれば回転で作成するポリサーフェスも滑らかになります.そこで丸ボタンの回転プロファイルを次の図のように作成するのですが,ボタンの側面と指で押される面に相当する曲線間に滑らかなフィレット曲線を作成します.

ボタンの側面と指で押される面に相当する両曲線に接する円を描画して円の接する点でトリムするとその間の曲線の接続も比較的バランス良く繋がります.円との接点でトリムされた両曲線間に「接続(BlendCrv)」で曲線を作成します.

  • 接続
    • メニュー: 曲線(C) > 接続(U)
    • コマンド: BlendCrv

「接続(BlendCrv)」内の設定で各接続点における接続条件を設定します.連続性を「曲率」もしくは「G3(曲率変化率)」を設定すると各設定に応じた滑らかな曲線が描画されます.各制御点は [ Shift ] キーを押しながらマウスでドラッグすると対象な点も同時に移動してくれるので便利です.曲線形状や曲率変化を見ながら制御点を調整して意図した形状で確定をします.

回転形状のプロファイル曲線が作成できたら「回転(Revolve)」 で曲線を回転したサーフェスを作成します.

  • 回転
    • メニュー: サーフェス(S) > 回転(V)
    • コマンド: Revolve

1回転分のサーフェスを作成するにはコマンド内で「360度(F)」を指定します.

右の図(ワイヤーフレーム表示)のようなボタン形状のサーフェスが作成されます.

1回転分作成したサーフェスをすべて選択して「結合(Join・Ctrl-J)」してソリッドモデル(閉じたポリサーフェス)にします.

<参考>
Rhinoceros 上では回転形状プロファイル曲線群を先に「結合(Join)」してから「回転(Revolve)」しても同じ形状になるのですが,サーフェスのフィレットのような回り込みの大きい形状と一体化したサーフェスをメッシュ化するときにフィレット形状付近を飛ばして粗いメッシュが作成されてしまうことがあります.フィレット形状部分のサーフェスは一体化したものとせずに別体のものを作成した後「結合(Join)」した方がフィレットサーフェスのエッジがメッシュ境界に反映されるので適切な形状のメッシュ作成のためには良いでしょう.

丸いボタンのソリッドモデルが作成できたら「配置(2点指定)・(Orient)」で洗濯機前面サーフェス上の法線方向に合わせてコピー移動します.先述したように軸回りの形状は軸対称で同じなので位置と方向1つのみの指定の移動変換をします.

四角いボタンと同様に前面サーフェスに少し食い込ませたいので丸いボタンの背面から 1mm 入ったところをコピー原点としてからボタンを押す方向軸上の1点を選択して,コピー先の洗濯機前面サーフェス上の点と法線方向を指定してスケーリングしない設定でコピー移動します.

  • 配置(2点指定)
    • メニュー: 変形(T) > 配置(O) > 2点指定(2)
    • コマンド: Orient

丸いボタンを洗濯機前面サーフェス上に配置したものを四角いボタンと併せて Perspective ビューにゴースト表示させたものが次の図です.

レンダリング表示にしてキャプチャした画像が次の図です.

洗濯機本体とボタン類のソリッドモデルをブーリアン演算して一体化するのは全ての作業の最後で良いので,他のモデリング作業の邪魔にならないようにとりあえず新しいレイヤー buttons(例)を追加してそこに移動しておきます.

今回の記事はここまでです.


本シリーズ次回の記事は引き続き滑らかなサーフェスの作例紹介で

「Gazebo/MoveIt のための 3D モデリング(7)滑らかなサーフェス – 作成編(その2)」

を予定しています.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(5)滑らかなサーフェス – 知識編

前回 「Gazebo/MoveIt のための 3D モデリング(3)基本形状編 – その2」 では洗濯機の基本的な形状で構成されるサーフェスのモデリングを行いました.

今回は発展的な内容として滑らかなサーフェスのモデリングに向けた予備知識的な内容の説明をします.

ロボットモデルはシミュレータ上で使うために結局メッシュ(ポリゴン)にしてしまうのでシミュレーションなどに利用する 3D モデル作成においては 「滑らかなサーフェス」 である必要性は高くありません.

しかし,モデリング対象の中には滑らかなサーフェスになるように設計されている製品もあります.そのような製品のモデリングの際に対象物の形状が円弧のように見えるけど何か違うので合わなくて悩むようなことがあります.そういったときに円弧などの基本的な形状以外のサーフェスもあることを知っていると,それは厳密には合わないものとして割り切って近似的に円弧などのシンプルな形状としてモデリングするということも適切に判断できると思います.

このようなことから,今回の記事はそういった 「滑らかなサーフェス」 について 「知る」 ことを目的としています.

「滑らか」とは?

「滑らか」 とはは何であるかというと,曲線やサーフェスの位置や接線方向,曲率,曲率の変化率に連続性があるということです.

上の図は 90° の角度をもつ直線間を曲線で接続させたときの連続性の違いによる曲率(黄色カーブ)のグラフ(CurvatureGraph)を表した画像をアニメーション化したものです.
(クリックで拡大)

各接続条件は次のリストのように連続性の条件が加わってゆくように考えてください.
「R形状」は「接線連続」のうち円弧で接続できる特殊なケースと捉えることができます.

  1. 位置連続
  2. 位置連続 + 接線連続
  3. 位置連続 + 接線連続 + 曲率連続
  4. 位置連続 + 接線連続 + 曲率連続 + 曲率変化率連続
  5. 位置連続 + 接線連続 + 曲率連続 + 曲率変化率連続 + 曲率変化率の変化率連続

上の図の接続連続性の異なる曲線を 「押し出し」 してサーフェスを作成してレンダリング表示にしたものが次の図です.

影の付き方が曲率や曲率変化率などの連続条件を加えてゆくと段々と滑らかになるのが見て取れるでしょうか?

サーフェスの曲率を解析して色で表した(CurvatureAnalysis)ものが次の図で,青が曲率が小さく,赤が曲率が大きいコンタ図になっています.

連続性の条件が加わるにつれて接続部周辺の曲率の変化が緩やかになっています.

また,サーフェスの滑らかさを評価するために 「ゼブラ(縞模様・Zebra)」 解析もわかりやすいのでよく利用します.

ゼブラ表示によりサーフェスの連続性がより強調されます.縞模様の通り方の滑らかさがサーフェスの接続性の滑らかさを表しています.サーフェスが滑らかに接続しているかどうかを評価したり,接続を滑らかに修正する際に役立ちます.

本シリーズの記事のモデリング対象として作成した洗濯機モデルの曲率とゼブラを表示したものが次の2つの図です.モデル全体で解析すると解析用のメッシュを細かく出来なくなるので,実際には接続性を評価する面に限って解析用メッシュをなるべく細かくして解析をするようにしています.

「制御点」と「次数」

実際に滑らかなサーフェスをモデリングする場合は,サーフェスが CAD やサーフェスモデラ内部でどのように表現されているかを理解しているとより意図したものに近いサーフェスを作成できるように思います.

Rhinoceros や一般的な CAD などでは曲線やサーフェスは NURBS (Non-Uniform Rational B-Spline/非一様有理Bスプライン) という数学的モデルで表現されています.

NURBS 以外にもサーフェスの 3D 表現モデルとして SubD (Subdivision/細分割曲面) もあります. SubD はコンピュータグラフィックス系の 3D モデリングソフトウェアで利用されていますが,機構設計分野ではあまり使われていませんので本シリーズの記事の対象としません.

NURBS で表現される曲線やサーフェスが何で構成されているかは大まかに述べますと 「制御点」 と 「次数」 です.

上の図は前項目で 90° の角度をもつ直線間を連続性の異なる接続をした曲線がそれぞれどのような 「制御点」 と 「次数」 で表現されているかを示した図をアニメーション化したものです.

NURBS カーブにおいてはその接続における連続性は次のリストにある各数の「制御点」により構成されています.

  1. 位置連続 → 端点の 1 点
  2. 接線連続 → 端点を含めたの 2 点
  3. 曲率連続 → 端点を含めたの 3 点
  4. 曲率変化率連続 → 端点を含めたの 4 点
  5. 曲率変化率の変化率連続 → 端点を含めたの 5 点

「次数(degree)」 は大きな数字になるほど曲線が滑らかになります.

上の図は 90° の角度をもつ直線間を接続した 「制御点:8 次数7 の曲線(曲率変化率連続)」 をあえて 「リビルド(Rebuild)」 して 「制御点:8 次数: 3 の曲線」 にして両端点の曲率を接続先の直線に 「マッチング(Match)」 した曲線の曲率の比較です.同じ制御点数でも次数が低いと曲線内で曲率の変化率の連続性が保てなくなってしまいます.

曲線に設定できる 「次数の最大値」 は 「制御点数 – 1」 です.

両端を曲率連続にするための 「制御点が6個」 の曲線の場合は 「次数の最大値は5次」,両端を曲率変化率連続にするための 「制御点が8個」 の場合は設定できる 「次数の最大値は7次」 になります.

制御点が多いとより細かく曲線やサーフェスの形状の制御が出来ますが,編集が大変だったり,データサイズが大きくなってしまうので,最小の制御点と適切な次数で表現したい形状や滑らかさを規定できるのがベストです.

接続条件は曲線の両端で同じである必要はないので,例えば片方の端は 「位置連続」 にして,もう片方の端は 「曲率変化率連続」 にするということも可能です.この場合の必要最小限の制御点は 「位置連続側: 1点」 と 「曲率変化率連続側: 4点」 と合わせて 「5点」 は必要になります.制御点を 「5点」 とした場合の次数の最大値を採って 「4次」 とするのが良いでしょう.

制御点が少なくて意図する形状が得られないようでしたら適宜制御点を多くして,次数もそれに合わせて大きくすると良いですが,次数の方は最大でも 「7次」 で十分なように筆者は考えています.

サーフェスにおける「制御点」と「次数」

これまで曲線を例に 「制御点」 と 「次数」 について説明してきましたが,サーフェスは曲線の「制御点」と「次数」を2方向に拡張したものです.

サーフェスは 「U方向」 と 「V方向」 の2方向がある 「四角い布」 をベースに,それを伸縮・曲げを行ったり,トリムしてその一部を使ったりするイメージとして捉えることができます.

円錐体のような三角形のサーフェスもありますが 「四角い布」 の特殊例と捉えることができ,同様にUV方向それぞれの要素があります.

次の図は本シリーズでモデリング対象とするために作成した洗濯機モデルのボディの角部のサーフェスの制御点と曲率のグラフを表示したものです.四方にある接続先のサーフェスとそれぞれ(なるべく)曲率変化率まで連続するように作成しました.そのため次数を 7次 とし,制御点を U方向に 15点,V方向に 8点 を持つサーフェスとしました.

  • 図示したサーフェス例の制御点と次数
    • U方向: 制御点 15点 / 次数 7
    • V方向: 制御点 8点 / 次数 7

曲線の連続性と同じように,サーフェスの連続性も各辺毎に作成時設定できるサーフェスもありますし,マッチングの際に異なる設定で各辺で行えば可能ですが,四辺の接続先と矛盾がないようにしないと隙間のないサーフェスにならない可能性もある点が曲線に比べて難しいところです.

どれほど滑らかにする?

さて,ロボットシミュレータのための 3D モデリングにおいてはどれほど滑らかなサーフェスを作成したら良いのでしょうか?

本記事冒頭で述べたように,ロボットモデルはシミュレータ上で使うために結局メッシュ(ポリゴン)にしてしまうのでシミュレーションなどに利用する 3D モデル作成においては 「滑らかなサーフェス」 である必要性は高くありません.

曲率連続や曲率変化率連続のサーフェスでモデリングしてメッシュ化してもそのような連続性に近い状態を維持しようとするとメッシュが細かくなりデータが重くなります.ただ,そのロボットシミュレーションをデモンストレーションやプレゼンテーションで綺麗に見せたく,少しメッシュデータが重くても良いような場合はなるべく滑らかなサーフェスをモデリングすることもあるように思います.

また,メッシュのデータ量の他にモデル作成の手間も考えておくべきでしょう.

下のリストにサーフェスの連続性の違いをまとめました.技術的なロボットシミュレーションが目的であれば 接線連続 までとしてモデリング時間を省くのも1つの方法です.大きな面はメッシュで形状が潰れてしまわないので 曲率連続 や 曲率変化率連続 まで考慮したモデルとして,小さな面はメッシュ形状に埋もれてしまうので R形状 や 接線連続 としてメリハリをつけるのも良いでしょう.

Rhinoceros では「曲率連続」までは標準の機能として普通に利用できるのでロボットシミュレータのための 3D モデリングでも用いるのはそんなに手間のかかることではないように思います.

  1. 位置連続
    • 面取り形状など
    • 機械設計的
  2. R形状
    • 隅R・角Rや.前回の記事で作成したフィレットのロフトなどが例
    • フライスや旋盤で機械加工された対象物のモデリングには必須
    • 機械設計的
  3. 接線連続
    • ロボット 3D モデリングの自由曲面では多く使う場合が多いか?
  4. 曲率連続
    • 滑らかに見えるサーフェス
    • デザイン的
    • 使っている CAD やサーフェイスモデラで機能的に簡単に作成できるようだったら用いてみるのもあり
    • Rhinoceros では
      • 標準で曲率連続にサーフェフをマッチング修正する機能がある
  5. 曲率変化率連続
    • より滑らかに見えるサーフェス
    • ロボット 3D モデリングでは知識として持っていたら十分
    • 使えると造形できるものが増える
    • デザイン的
    • Rhinoceros では
      • 「作成時」に曲率変化率連続性のある曲線・サーフェスを作成する機能はある
      • 「修正時」は曲率連続マッチング機能を利用した後に,加えて曲率目視で4番目の制御各点を手動調整する必要があるので手間
        • (この曲率変化率連続マッチングを行う Plug-in があれば筆者にも教えて欲しい!)
  6. 曲率変化率の変化率連続
    • Rhinoceros では
      • 「作成時」に曲率変化率の変化率連続性のある曲線・サーフェスを作成する機能はある
      • 「修正時」に曲率目視で5番目の制御点まで手動調整するのは難しいか?

今回の記事はここまでです.

大体どのような滑らかさのサーフェスの種類があって,CAD やサーフェスモデラでそれを作成するために必要な条件や作成の手間のイメージが伝わっていると良いのですが.


本シリーズ次回の記事は

「Gazebo/MoveIt のための 3D モデリング(6)滑らかなサーフェス – 作成編」

を予定しています.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(4)基本形状編 – その2

前回 「Gazebo/MoveIt のための 3D モデリング(3)基本形状編 – その1」 で Box 形状や球面で洗濯機の大きな面のモデリングを行いました.

今回はその続きで,洗濯機の背面や底部のモデリングを行います.

今回のゴールは左の図の状態です.

ボディ背部のモデリング

ボディ背面の突出形状部の最後部から 40mm の幅がありますので,洗濯機の主要形状部をその分トリムします.(前回 Box 形状の Scale1D を前後方向に行わずにガムボール移動などで World 座標系で X の正方向に 40mm 移動した場合はこの手順は不要)

洗濯機の側面視で最背部から垂直な直線を描画して,前方向に 40mm 移動させ,この直線を使ってボディをトリム(Trim)します.

Perspective ビューでトリムされたボディのエッジ分析(ShowEdges)をすると次の左の図のようになります.

このような開口部エッジが同一平面内にあって閉曲線になっている場合は平面で塞ぐ 「キャップ(Cap)」 を実行できます.

  • キャップ
    • メニュー: ソリッド(O) > キャップ(H)
    • コマンド: Cap

「キャップ」の実行結果

同一平面内にある閉エッジ

次は背部に突出している形状を作成します.

背部の台形状の輪郭を描画します.上面視(Top ビュー)で座標 (-320,0) から水平の直線(Line)を後部に向かって描画して,その直線からオフセット(Offset)で オプション Both(B) で両サイドへのオフセット線をオフセット量 260[mm] = 520mm/2 で描画します.メインボディ最後部のエッジと 260mm オフセットした両直線の交点を中心に回転(Rotate)で 45°,-45° を指定して回転させて上面図画像の輪郭に合うことを確認します.

  • 回転
    • メニュー: 変形(T) > 回転(R)
    • コマンド: Rotate

また直線(Line)を座標 (-360,0) から「両方向(B)」を指定して洗濯機の幅方向に描画します.

描画した3つの直線の台形での不要部分を互いにトリムします.

Perspective ビューに移って台形の開いている部分を直線で接続して結合(Join)して曲線を閉じます.閉じた曲線を洗濯機の高さ方向(Z方向)に +90mm ガムボールで移動させます.

背面部の基礎となる 3D 形状をソリッドの「垂直に押し出し」で作成します.とりあえず上面高さまで押し出しします.

  • 垂直に押し出し
    • メニュー: ソリッド(O) → 平面曲線を押し出し(X) → 直線(S)
    • コマンド: ExtrudeCrv

洗濯機の側面視(Back ビュー)にて洗濯機背部形状の上端に相当する斜めの直線を描画して先程「押し出し」したソリッドを トリム(Trim) して キャップ(Cap) で閉じます.

洗濯機背面部上面でトリムしたモデル(キャップの前の段階)

三面図から読み取れる形状としてはここまでなのですが,洗濯機背部は角部や隅部に 「R形状」 や 「フィレット」 と言われる形状がつけられていることが多いです.

今回は三面図から 「R形状」 の寸法は読み取れないので,それを想像して寸法を決めて 「ロフトサーフェス(Loft)」 で作成します.

ロフトサーフェスは曲線と曲線の間にサーフェスを作ります.ロフトサーフェスの基となる曲線を 「フィレット(Fillet)」 で描画します.

  • フィレット
    • メニュー: 曲線(C) → フィレット(F)
    • コマンド: Fillet

フィレットの半径は最後部の平面上の方に 40mm メインボディとつながる平面上の方に 80mm のフィレットをかけるとバランスの良さそうなロフトサーフェスになるかと思います.

  • ロフト
    • メニュー: サーフェス(S) > ロフト(L)
    • コマンド: Loft

洗濯機背部上方のコーナー部にロフトでフィレットサーフェスが作成できたら ミラー(Mirror) でX軸対称に反転コピーします.そしてミラーリングして左右2つになったフィレットで洗濯機背部のソリッドモデルを トリム(Trim) します.

2つのフィレットとそれらでトリムされた背部ポリサーフェスを 結合(Join) すると1つのソリッド(閉じたポリサーフェス)になります.

洗濯機のメインのボディと背部の2つのソリッドモデルは互いに接し合っているので2つのソリッドの 「和の演算(BooleanUnion)」 を行って1つのソリッドモデルにします.

  • 和の演算
    • メニュー: ソリッド(O) > 和(N)
    • コマンド: BooleanUnion

1つのソリッドになるので,くどいようですが ShowEdges で 「閉じたポリサーフェス(=ソリッド)」 であることを都度確認すると良いでしょう.

ボディ底部のモデリング

Gazebo や MoveIt のモデルとしては洗濯機底部はロボットとインタラクションすることはあまりないと思いますので大体の雰囲気をモデリングできれば十分です.

洗濯機の足部は三面図だけではなくカタログ画像からも少し形状が分かるので三面図と併せて参考にしてモデリングします.

カタログ画像や3面図から,足部はテーパのかかった円錐台形状であろうと思われます.

側面視や前面視からそれぞれの足の中心座標を推定し,下面と上面の直径はそれぞれ 50[mm] と 54[mm] ぐらいと当たりをつけて円を描画してロフトとキャップを組み合わせてソリッドモデルを作成します.

洗濯機底部の足以外のサーフェスのモデリングの大まかな様子は次の GIF アニメーションのような感じです.モデリングの履歴(ヒストリー)を使わないモデリングなので作成手順はやり易い順番で大丈夫です.またサーフェスの作成方法も1通りしかないのではなく,例えば 「ロフト (Loft)」 でフィレット形状を作成する代わりに 「サーフェス > フィレット(FilletSrf)」 や 「エッジをフィレット(FilletEdge)」 ,「回転(Revolve)」 を使ったりすることもできます.

これまで取り上げていない機能で利用したのは 「曲線を押し出し(ExtrudeCrv)」 と 「円柱(Cylinder)」 の機能です.

  • 曲線を押し出し
    • メニュー: サーフェス(S) > 曲線を押し出し(X) > 直線(S)
    • コマンド: ExtrudeCrv
  • 円柱
    • メニュー: ソリッド(O) > 円柱(Y)
    • コマンド: Cylinder

今回は洗濯機ボディの背部や底部のモデリング方法を紹介して,次のモデルとなり本記事のゴールに到着しました.

基本形状編はひとまず今回の記事までです.

本シリーズ次回の記事は 「Gazebo/MoveIt のための 3D モデリング(5) 滑らかなサーフェス – 知識編」 を予定しています.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(3)基本形状編 – その1

前回の Gazebo/MoveIt のための 3D モデリング(2)準備編 で右の図のように三面図を空間上に配置してモデリングの準備を行いました.

今回から実際のモデリング方法の紹介を行います.今回のゴールは基本的な形状で構成された次の図の状態です.

直方体と球面でモデリングします.

レイヤ03 を model という名前にしてこのレイヤ内でモデルを作成します.

レイヤ model をダブルクリックするとチェックマークが model レイヤに付いて編集対象のレイヤになります.

直方体

三面図を見るとボディの上面や側面,1段上がった底面,背面から1つ前方の面は平面で構成されているようです.(筆者が基のモデルを作成しているので自作自演なのですが…)

直方体を描画してそれらのサーフェスとします.

まず,準備で描画した outline のボックスを利用して直方体を描画するために Osnap の「端点」のチェックを入れておきます.

直方体は Box コマンドかメニューから ソリッド(O) > 直方体(B) > 2コーナー,高さ指定(C) を選択して描画します.

outline の底面の対角点の2点と高さとして上面のいずれかの角の点を順次クリックすると右の図のように直方体が描画できます.

次に,1方向のみのスケーリング Scale1D( 変形(T) > スケール(S) > 1Dスケール(1) )を使ってボディの底面の位置と奥行方向の2つの寸法調整をします.

底面の位置は Scale1D で上面位置を基準として変形を行います.

奥行方向の寸法調整は後で前面のサーフェスを作成してそれでトリムをしたいので少し前に出しておきます.もしくは背面の突出している部分の基となる面がが 40mm 分前に来るのでガムボール移動で X方向 に 40mm 移動するようにしても大丈夫です.

前面のサーフェス(球面)

洗濯機前面の主なサーフェスは球面ぽい感じがします.(自作自演ですが…)

まずは球面の半径を推定します.

洗濯機の側面視,Rhinoceros での Back ビューにて外形カーブに沿って3点指定の円を描画しておおよその半径を見てみます.

3点指定の円はメニューからは 曲線(C) > 円(C) > 3点指定(3) で実行し,コマンドでは Circle を実行してから 3点(O) を指定します.アイコンからは円形状から3点付いた円を選択します.

洗濯機前面の側面視への投影輪郭線上の3点を指定して円を描画します.

次に描画した円の半径を調べます.メニューからは 解析(A) > 半径(R) を選択し,コマンドの場合は Radius を入力して描画した円を選択します.その結果,先程描画した円の半径は「5182.7590331 ミリメートル」と解析されました.

切りの良い数字で半径は 5000mm ぐらいかな?と当たりをつけて球を描画してみます.

描画した円を削除します.

先程と同じメニュー・コマンドの「3点指定の円」を利用しますが今度は投影輪郭線上の2点と半径 5000mm を指定して描画します.

描画した半径 5000mm の円の中心から半径 5000mm の球を作成します.Osnap の「中心点」にチェックを入れます.球を作成するにはコマンドでは Sphere,メニューからは ソリッド(S) > 球(S)です.球の中心に円の中心を選択して半径 5000mm を指定して球を作成します.

このように側面視では半径 5000mm の球であるとみなしましたが,他の投影図で見ても洗濯機前面のサーフェスとしてこの半径 5000mm の球面が妥当であるか? を見てみます.

上面視(Rhinoceros の Top ビュー)から見ても半径 5000mm から作られる形状が三面図と整合するかを確認します.

作成した球を上面の高さでトリムします.上面高さで水平な直線を描画してそれを用いてトリムします.

側面視で Line コマンドかメニュー 曲線(C) > 直線(L) > 線 もしくはアイコン選択で直線描画を開始し,Osnap の「端点」にチェックが入っている状態で上面の1つの頂点を選択して Shift キーを押しながら水平に直線を描画します.

トリムに用いる直線を選択した状態でキーボードショートカット Ctrl+T を選択するとトリムコマンドが開始されますのでトリム対象として球のサーフェスを選択します.この時トリム設定として ( 切断線を延長(E)=はい 仮想交差(A)=はい ) として実行します.

  • トリム
    • キーボードショートカット: Ctrl+T
    • コマンド: Trim
    • メニュー: 編集(E) > トリム(M)

次の図は洗濯機の上面高さでトリムした球面サーフェスの上端エッジの円とその上端エッジを曲線として複製( DupEdge )してその曲線を洗濯機の上面図上の曲線とほぼ重なる場所にオフセット( Offset )させた比較です.

  1. DupEdge : メニューからは 曲線(C) > オブジェクトから曲線を作成(F) > エッジを複製(E)
  2. Offset : メニューからは 曲線(C) > オフセット(O) > 曲線をオフセット(O)
    • → コマンド内の指定で 通過点指定(T)

(画像クリックで拡大) オフセットさせた曲線は洗濯機上面図の曲線と比べて少し半径の大きい円のようですので,実際の洗濯機の前面の球面の半径は 5000mm よりも小さい可能性が高いです.

洗濯機前面を球面とした場合は 5000mm では少し半径が大きいようですので,半径 4000mm の球面として半径 5000mm で行ったのと同じように描画して評価する手順を再び行います.

  1. 側面視で洗濯機前面の投影輪郭線上の2点と半径 4000mm を指定して円を描画
  2. 描画した半径 4000mm の円の中心に半径 4000mm の球を作成
  3. 半径 4000mm の球面を洗濯機上面の高さでトリム
  4. 上面視でトリムした球面エッジからオフセットカーブを洗濯機上面図の曲線にほぼ一致する位置に描画
  5. オフセットカーブの曲率などから妥当なサーフェスか否かを判断

洗濯機前面サーフェスを半径 4000mm の球面とした場合の洗濯機上面図とトリムされた球面エッジを比較したのが次の図です.

画像では「上端エッジからオフセットした曲線(円)」が黄色になっているので少し見づらいかもしれませんが洗濯機の上面図内の曲線と一致しているように見えます.(画像クリックで拡大) このことより洗濯機前面のサーフェスを半径 4000mm の球面としたのはおおよそ妥当だと判断しました.

Perspective ビューで少し広めにモデリング空間を表示させたのが次の図です.ボックスと上面高さでトリムされた球面が見えています.

側面視で描画した円は オブジェクトを非表示( メニュー: 編集(E) > 表示(V) > 非表示(H) / コマンド: Hide ) にしてしまっても良いかもしれません.

球面とボックスを互いにトリムして洗濯機のボディ形状に近づけます.

ボックスを球面サーフェスでトリム

球面サーフェスをボックスでトリム

互いに完全に交差した球面サーフェスと Box サーフェスを互いにトリムしたので両者間に隙間はないはずです.ただ,これらは結合(Join)して一体化していないので,状態としては隙間なく互いにただ並べられている状態,英語では Watertight(水密)などと言われる状態です.
それを確認するために解析ツールで「エッジを表示」してみます.

  • エッジを表示
    • メニュー: 解析(A) > エッジツール(E) > エッジを表示(E)
    • コマンド: ShowEdges

エッジ分析の小ウィンドウが表示されたらその中の 表示 – オープンエッジ(N) を選択します.
トリムされた球面サーフェスと Box サーフェスの境界部分が明るい紫からピンクのような色で表示されると思います.

トリムした球面サーフェスと Box サーフェスを結合(Join)してオブジェクトとして1体化ます.両方のサーフェスを選択してから結合を実行します.

  • 結合
    • メニュー: 編集(E) > 結合(J)
    • コマンド: Join
    • キーボードショートカット: Ctrl+J

結合したら再び「エッジを表示」を実行してオープンエッジがないかを確認します.
オープンエッジがなかったら次のようなエッジ分析の結果が出るかと思います.

合計??個のエッジ.オープンエッジ,非多様体エッジはありません.

これは 「閉じたポリサーフェス」 や 「ソリッド」 と呼ばれるモデルの状態を意味します.

今後「ソリッドモデル」や「閉じたポリサーフェス」であるはずのモデルを作った場合は都度確認するようにしましょう.都度確認しないで何かのはずみで僅かな隙間が残っているまま作業を進めてしまうと後々にサーフェスが閉じなくなり多くの作業をやり直さないとならなくなってしまうことがあります.

  • Tips
    • 「ソリッドモデル(閉じたポリサーフェス)」 モデルにしておくと Gazebo や MoveIt だけではなく有限要素解析による応力計算や数値流体解析などにも応用することができます.

次に前面下部のサーフェスも似たような面であろうと目論んで,側面視(Rhinoceros の Back ビュー)で半径 4000mm の円を描画してみると,大体 Z=350mm ぐらいの平面で上下反転しているように見えます.

球面サーフェスを反転コピーするために先程結合したソリッド(閉じたポリサーフェス)モデルから 「サーフェスを抽出」 して球面サーフェスを分離します.

  • サーフェスの抽出
    • メニュー: ソリッド(O) > サーフェスを抽出(A)
    • コマンド: ExtractSrf

球面サーフェスを反転コピーするために側面視(Rhinoceros の Back ビュー)で直線(Line)を座標 (0,350) から水平に引きます.

前面の球面サーフェスを選択してミラー変形で反転コピーします.

  • ミラー
    • メニュー: 変形(T) > ミラー(I)
    • コマンド: Mirror

座標 (0,350) から水平に引いた線の両端を順に選択して反転させます.

座標 (0,350) から水平に引いた線で不要になる球面サーフェスをトリムし,またミラーコピーした球面サーフェスの Box サーフェスからはみ出る部分や Box サーフェスのはみ出る部分を球面サーフェスでトリムします.

これらのトリムされた上下の球面サーフェスと Box サーフェスを結合(Join)すると次のようなソリッド(閉じたポリサーフェス)モデルになり,記事の冒頭で述べたゴールに辿り着きました.

次回は, 「3D モデリング(4)基本形状編 – その2」 として洗濯機の背面や底部の形状のモデリングを説明する予定です.

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(2)準備編

今回の記事のゴールは右の図のようにモデリングに必要な情報を Rhinoceros 上に表示することです.

筆者はこの作業を「召喚の儀式」と勝手に呼んでいます.召喚の儀式を行っても勝手にモデルが湧き出てくるわけではないのですが,このようにすることで効率的にモデリングができると考えています.

作業の流れは次のようになっています.

  1. カタログ → 各投影図の画像
  2. Rhinoceros 上に外形寸法大の直方体を描く
  3. 各投影図画像を空間上に配置

1. カタログ → 各投影図の画像

まず,カタログの PDF ファイルから寸法図の各投影を画像として切り取ります.カタログが紙面の場合はスキャナで画像として取り込むと良いでしょう.

本例では右の図をクリックするとサンプルカタログ画像が表示されるのでそれを使ってください.また同じサンプルカタログの PDF ファイルは下記リンク先にあります.

正面図 左側面図 上面図

Windows の場合は一例として次のように PDF ファイルから投影図を画像として保存します.

  1. PDF ファイルを「Acrobat Reader」で表示
  2. 画像保存したい部分を拡大して
  3. Win+PrintScreen キーで画面をクリップボードにコピー
  4. ペイントを開いて「貼り付け」
  5. 必要部分を選択してトリミング

Mac の場合は同様のことを次のように行います.

  1. PDF ファイルを「プレビュー」アプリで開く
  2. PNG 画像でエクスポート
  3. 2. で保存した PNG 画像ファイルを「プレビュー」で開く
  4. 「プレビュー」でトリミング編集して各投影図としてそれぞれ保存

2. Rhinoceros 上に外形寸法大の直方体を描く

各投影図の画像を Rhinoceros の空間上に配置する前に画像のサイズ調整やモデリングしながら大きさの確認をするなどの目的のためモデリング対象の外形寸法を反映した線画(ワイヤーフレーム)の直方体を描画します.

Rhinoceros を起動します.起動時に表示されるテンプレートの Small Millimeters か Large Millimeters を開きます.

Rhinoceros の設定変更は必須ではないですが,洗濯機のような大きさの対象物の場合は次のようにしておくのをお奨めします.設定変更はメニューバーの ファイル(F) → プロパティ(R)… を選択して小ウィンドウ「ドキュメントのプロパティ」内で行います.

  • グリッド
    • グリッドのプロパティ
      • グリッド線数(E): 200
      • 細グリッド線間隔(G): 10.0 ミリメートル
      • 太グリッド線間隔(M): 10 本(細グリッド線)
    • グリッドスナップ
      • スナップ間隔(S): 0.5 ミリメートル
  • 単位
    • 単位と許容差
      • 絶対許容差(T): 0.001 単位
      • 角度許容差(A): 0.01 度
    • 距離表示
      • 距離の精度(E): 1.0000

それでは Rhinoceros 空間内にモデルの外形寸法に対応したワイヤーフレームボックスを描画します.

分かりやすいように「レイヤ 01」の名前を本記事では「outline」に変更します.レイヤ名は何でも良いです.

  • 「レイヤ 01」上で右クリック → レイヤ名を変更
    • もしくはレイヤ名上でダブルクリック

そして outline レイヤ名の少し右をクリックしてチェックマークを入れて編集対象にします.

カタログの3面図を見ると洗濯機のボディの高さが 1050 [mm],主な幅が 640 [mm],奥行きが 720 [mm] とあります.

またロボットの座標構成は一般的には次のようになっています.

  • ロボットの一般的な座標構成
    • X 正方向 : 前
    • Y 正方向 : 左
    • Z  正方向 : 上

なお,Rhinoceros はどのような分野の座標系を採用しているのか分かりませんがビューの名前とロボットの一般的な座標構成による前後・左右が異なっているので,それはそれとしてビューの名前は気にしないこととします.

このように製品の外形寸法やロボットの一般的な座標構成をふまえて,最初に XY 平面上に原点を重心とする X方向 740 [mm] Y方向 640 [mm] の長方形を描きます.

  1. Top ビューを選択
  2. 曲線(C) → 長方形(G) → 中心,コーナー指定(N)
  3. 「長方形の中心: 」に 0,0 を入力して Enter キーを押す
  4. 長方形の X 方向の長さ(=奥行き)の 720 を入力して Enter キーを押す
  5. 長方形の Y 方向の長さ(=幅)の 640 を入力して Enter キーを押す

次に XY 平面上に描いた長方形を洗濯機の上面に相当する上方に 1050 [mm] コピー移動させます.このような単純な移動は Rhinoceros の「ガムボール」を利用すると良いでしょう.Rhinoceros ウィンドウ内の下の少し右の方に「ガムボール」ボタンがあるのでクリックして有効にします.

  1. Perspective ビューを選択
  2. XY 平面上に描画した長方形をクリックして選択(選択されるとガムボールも表示される)
  3. コピー移動するために Alt キーを押しながら Z 軸(青)をクリック
  4. テキストボックスが表示されるので移動量 1050 を入力して Enter

ガムボールを用いた移動は Ctrl や Alt キーを組み合わせることで次のように働きます.

  • ガムボールの要素をクリックのみ → 選択オブジェクトの移動
  • Alt キー + ガムボールの要素をクリック → 選択オブジェクトのコピー移動
  • Ctrl キー + ガムボールの要素をクリック → 選択オブジェクトのガムボールのみ移動

また,ガムボール以外のコピー移動の方法としてメニューバーの 変形(T) → コピー(C) もしくはコピーのコマンド入力「Copy」もあります.この場合は移動する始点と終点の座標を順次入力してコピー移動させます.

あとは2つの長方形の各頂点間に直線を描画して線で構成された直方体にします.

次の図のようにオブジェクトスナップ Osnap をオンにして端点にスナップするようにチェックを入れると描画時に端点にスナップして正確に描画することができます.

直線コマンドはメニューバーからは 曲線(C) → 直線(L) → 線 で,コマンド入力では Line で実行できます.
(画像をクリックすると大きな画像として表示されます.)

線で構成された直方体が描画できたらそれをグループ化しておきます.全体を選択してメニューバーからは 編集(E) → グループ(G) → グループ化(G) で,コマンド入力では Group で,キーボードショートカットでは CtrL + G でグループ化できます.

後で各投影図画像を配置する時に補助線を描き加えますが,とりあえず outline レイヤーは編集終了ということでロックします.

現在編集中のレイヤはロックできないので「レイヤ 02」を次の各投影図画像を貼り付けるためのレイヤーとして名前を「drawings」などに変更して,drawings レイヤのチェックマーク列をクリックして編集レイヤを変更します.その後で outline レイヤの鍵マークをクリックして南京錠が掛かったアイコンに変更するとレイヤーがロックされます.

 

3. 各投影図画像を空間上に配置

本記事冒頭の「召喚の儀式」の図で示したように Rhinoceros では空間上に画像ファイルを平面として配置することができます.

まず,正面の投影図の画像を配置してみます.

Rhinoceros のビュー名の設定は一般的なロボット座標系の投影面名と異なるので,ロボット座標系から考えると「正面図」は YZ平面(とその平行面)なので,Rhinoceros では Right ビューに相当します. ビューの下にある Right タブを選択したり,Perspective などのビュー左上のビュー名をダブルクリックするなどして4ビュー画面にしてからビュー名 Right をダブルクリックして Right ビュー1つの表示にします.

ビュー左上のビュー名 Right をダブルクリックして全体に表示します.

画像はメニューバーから サーフェス(S) → 平面(P) → ピクチャー(E)  を選択すると「ビッマップを開く」子ウィンドウが開くので正面図の画像ファイルを選択して開きます.

すると「ピクチャーの1つ目のコーナー」を指定するよう言われるので,位置やスケールは後から outline レイヤの直方体に合わせるので,ここでは適当にクリックして指定します.次に「もう一方のコーナーまたは長さ」を指定するように言われるので,Ctrl キーを押しながらマウスを動かすと画像の縦横比を保持したままもう一方の頂点の座標が変化しますのでここでも先ずは適当にクリックしてピクチャーを配置します.

画像の位置とスケールを合わせてゆきます.

まずはコーナー位置を合わせます.正面図のピクチャオブジェクトを選択してオブジェクトを直接ドラッグしたりガムボールを使って移動させ,右の図の例では洗濯機の正面図の右下コーナーを外形の直方体の角に合わせています.画像ファイルなので線はある程度ドット幅を持っていると思いますが,outline レイヤの外形の直方体の線と画像上の線の大体の中心が合うようにします.

次は画像のスケールを outline レイヤの外形の直方体の大きさに合わせます.スケールはまず縦横2方向同時スケールの操作をします.縦横どちらで合わせても良いですが寸法が長い方が相対的に精度がでやすいので縦方向で合わせます.メニューバーからは 変形(T) → スケール(S) → 2Dスケール(2) ,コマンドでは Scale2D で実行します.

  1. 正面図のピクチャを選択,他はスケールしないので選択終了
  2. 正面図右下コーナーをスケールする原点に指定(レイヤーにロックが掛かっていてもオブジェクトスナップはできる)
  3. スケールする元の方向と長さを指定するために,Shift を押しながらマウスを動かすとポインタがスケール原点から縦もしくは横に動きが限定されるので,Shift キーを押しながら正面図ピクチャの上辺付近をクリックして指定
  4. 正面図ピクチャの上辺が outline レイヤの外形直方体の上辺に一致するようにしてクリックでスケール決定

画像ファイルはドット絵(ラスターデータ)であることや,カタログの印刷とスキャンや PDF 化の過程で図形としての縦横比が必ずしも保たれているわけではありません.そのため先ほど 2D スケールを行って縦方向のスケールは合ったものの横方向を確認してみると少しずれているようなことが結構あります.その場合は1方向のみの Scale1D コマンド(メニューバーなどからも選択可能)で横方向のみスケール調整します.

このように位置とスケールを合わせるのですが1回でバシッと綺麗に決まらないことがままあるので何回か「位置」「スケール」を繰り返して調整します.

配置した正面図ピクチャーをビューを Perspective に変更して見てみると,おそらく YZ 平面上にあると思います.この場所だとこれから 3D モデルを作るスペースのド真ん中にあり邪魔になるので X 軸方向に -1500[mm] ぐらい移動させておきます.

あとは同様にして「左側面図」と「上面図」の画像を Rhinoceros 上のピクチャとして配置します.

「左側面図」は一般的なロボット座標系では XZ平面 を Y軸 のプラス方向からマイナス方向へ見ることになります.この視点は Rhinoceros の設定では Back ビューに相当します.Rhinoceros のデフォルトでは Front ビューが最初のセット内に設定されていると思いますのでこれを Back ビューにします.ビュー内左上のビュー名右隣にある ▼(逆三角形)マーク → ビューの設定(V) → Back(A) で変更します.

今回の作例用に用意した「左側面図」には最も外側の寸法だけではなく洗濯機の設置検討に参考にする寸法を入れてあります.外寸のスケール調整をした後に寸法の数値から拾った線を outline レイヤーに加えて寸法補助線と重なるかを見て,スケールが合っているか?,位置が合っているか?など確認できます.今回は洗濯機の設置脚が付いている下部ボディの前端の寸法の数値を拾うと最後部から前方に 605[mm] ( = 550 + 55 )の位置だということが分かりますのでそこに線を引き該当する寸法補助線と重なるかを確認しました.

最初の各投影図をレイアウトする時点で間違ったまま進めてしまうとているとそのあとモデリングしたものが使えないものになってしまう可能性があるので注意してください.

他のヒストリータイプの CAD では遡って修正することも可能ですが,時として修正可能なパラメータから外れてモデルが成立しなくなる可能性も無きにしもあらずなのでまず最初の段階で寸法等確認しながら先に進むことをお薦めします.

Rhinoceros ではオブジェクトの位置や距離を確認するにはメニューバーの 解析(A) → 点(P)距離(D) で確認しながら進めると良いでしょう.

「上面図」は Rhinoceros でも Top ビューですので X方向,Y方向 を間違わずに配置すれば問題ないと思います.

さてこれで本記事の最初に述べました「召喚の儀式」が整いました.

おそらく本記事の読者に召喚術を使える「魔術師」や「陰陽師」の方はそう多くはないと思いますので,次回の記事からは今回準備した「召喚の儀式」の情報を基に地道に Rhinoceros 上で 3D モデリングを行う方法をご紹介したいと思います.


<追記:つづきの記事>

著者:yamamoto.yosuke

Gazebo/MoveIt のための 3D モデリング(1)はじめに

ROS で Gazebo や MoveIt を利用してシミュレーションや動作計画を行うときに既存のモデルも多くあり重宝しますが,それだけでなくて実際の製品の形状でシミュレートしたいと思う場面もあるかと思います.

モデリング対象物の実物があれば 3D スキャンすることもあるでしょうがメッシュを整える手間があったり,実物がない場合もあります.

そこで本シリーズでは複数回の記事に分けてカタログにある図面から 3D モデリングを行って Gazebo / MoveIt で利用できるようにするにはどのようにするのかの例をご紹介します.

モデリング作業のゴールとなる Gazebo と MoveIt のモデルに必要なデータを次の表にまとめます.

Gazebo モデル MoveIt モデル
モデルファイル URDF or SDF ファイル URDF ファイル
チュートリアル

最終的に必要になる URDF ファイルや SDF ファイルは XML データのファイルですのでテキストエディタなどで編集します.

URDF と SDF に内包するのに利用可能な 3D モデルデータを次の表に示します.

SDF ファイル URDF ファイル
モデルファイル STL ファイル Collada or STL ファイル
表示用メッシュ
  • 色毎の STL メッシュファイル
  • メッシュは開いていても良い
  • Collada (*.dae) モデルファイル
  • STL メッシュファイルも可
干渉用メッシュ
  • 干渉範囲を定義する閉メッシュ STL メッシュファイル
  • 干渉チェックに問題ない範囲であれば表示用メッシュよりも粗くしてデータを軽くすると良い
  • 干渉範囲を定義する閉メッシュ Collada (*.dae) モデルファイルもしくは STL メッシュファイル
  • データ量的に重くなければ表示用メッシュをそのまま使っても良い

それぞれ画面表示用とロボットリンク干渉チェック用のデータは別々に設定されるので必要に応じてメッシュの粗密を調整します.SDF ファイルの表示用メッシュは Collada (*.dae) フォーマットも使えるのですが色情報が表示に反映されないのでここでは色ごとに分けた STL フォーマットファイルを作ります.

全体の作業と記事の流れは次のようになります.

  1. 全体の工程(本記事)
  2. カタログ図面のキャプチャとモデリング空間への配置
  3. 基本的な形状の作成
  4. なめらかなサーフェスの作成(オプション)
  5. MoveIt モデルの作成
  6. Gazebo モデルの作成

今回のモデリング例の対象物は多くの製品でカタログに寸法図が掲載されている洗濯機としました.

意匠権や著作権などの侵害がないように本記事の作例用に予めドラム式洗濯機モデルを作成してそれから寸法図を含む模擬的なカタログの PDF ファイルを作成しました.

  • 注) 読者の皆さんも実在する製品・商品をモデリングして公開する際には意匠権や著作権などの侵害が無いようにしてください.

このカタログの図面を元にレンダリング図も参考にしながらモデリングします.本記事筆者が対象モデルを作りカタログ化して再びモデリングするということで自作自演になってしまいますが,モデリングの流れや方法をお伝えするためと思ってご容赦ください.

本シリーズの記事では 3D モデル作成ソフトウェアは Rhinoceros 7 を用いています.Rhinoceros は CAD ソフトウェアの1つと言えますが,どちらかと言うとサーフェスモデラに近いと思います.各機能自体は読者が使っている CAD と共通点があると思いますのでコマンド等それぞれ置き換えて読んでいただけるとありがたいです.

参考のため比較的安いもしくは無料で入門的にも利用可能な CAD を次の表に示します.

Rhinoceros 7 Fusion 360 FreeCAD
URL https://www.rhino3d.co.jp/ https://www.autodesk.co.jp/products/fusion-360/ https://www.freecadweb.org/
商業利用 158,400円 61,600円/年 無料
教育利用 39,600円 1年間無償 無料
モデリング 非パラメトリック*注 パラメトリック パラメトリック
  • 注) ヒストリーや Grasshopper,Rhino-Python でパラメトリックモデリングも可能

モデリングでは下記リストのような色々なサーフェス要素の作成方法を説明する予定です.

  • 基本的な 3D 形状(平面・円筒・球など)
  • 各種フィレット
    • 接線連続の単純Rフィレット(機械設計様形状)
    • 曲率連続フィレット
    • 曲率変化率連続フィレット
  • 自由形状サーフェス

このうち曲率連続や曲率変化率連続のサーフェスは Gazebo や MoveIt で利用する場合は結局メッシュデータ( STLメッシュ / Collada も内部ではメッシュ )になってしまうので必須ではないですが参考までに紹介します.

本シリーズ,次回はモデリングの準備編です.

著者:yamamoto.yosuke

トランジスタ技術 2020年9月号 の ROS 入門の記事を執筆しました

トランジスタ技術 2020年9月号https://toragi.cqpub.co.jp/tabid/918/Default.aspx )の ROS 入門の記事を執筆しましたのでご紹介します.

東京オープンソースロボティクス協会は次の章を執筆しました.

(各章リンク先にサンプル PDF ファイルがあります)

これらの章では TORK の ROS ワークショップなどでつまづきやすかった点を踏まえて,次の内容をなるべく分かりやすく書いたつもりです.

  • ROS の概要や使うメリット
  • ROS の学習入門時のパソコンの選定
  • ROS を実行する Ubuntu Linux OS のパソコンへのインストール
  • ROS やロボットシミュレータのインストール方法とその利用
  • ROS のロボット動作計画・実行プログラムの実行や改造

続きを読む

著者:yamamoto.yosuke

パソコン1台で出来るロボットの学習素材集

ROS(ロス/Robot Operating System)の学習は実際にロボットがなくてもロボットのシミュレータが入手できるのでネットワークにつながるパソコンが1台あればできますので結構自習に向いています.この記事では ROS の学習を始める,進めるにあたり必要な情報がある Web へのリンクを中心に紹介します.

大まかに言うと次のインストールを行えば ROS の学習をスタートすることができます.

  • パソコンにオペレーティングシステムの Ubuntu Linux をインストール
  • Ubuntu Linux に ROS をインストール
  • ROS 上で動くロボットソフトウェアのインストール
    • → 紹介 ROS チュートリアル内にて

ROS と Ubuntu Linux のバージョンは後述する ROS 学習のチュートリアルが現時点では ROS Kinetic というバージョンを基本としているので下記の組み合わせをお勧めします.

  • Ubuntu 16.04
  • ROS Kinetic

ROS Melodic は ROS Kinetic と基本的な操作のほとんどは変わらないので ROS Kinetic で学習してから ROS Melodic に移行しても難なく可能です.

 

パソコンへの Ubuntu Linux のインストール

パソコンはどのようなものを使えば良いのか?については下記記事を参考にしてください.

ROS 導入ノートパソコン比較調査

ROS 導入ノートパソコン比較調査

最新高性能パソコンよりも数年型落ちや廉価の機種のほうが Ubuntu Linux をインストールしやすい傾向にあるように思います.

 

Ubuntu Linux への ROS のインストール

下記リンク先に各 ROS のバージョンにおけるインストール手順が書かれています.

また,Ubuntu のバージョンと ROS のバージョンには1対1の対応関係があるので組み合わせを気をつける必要があります.

 

ROS のチュートリアル

各チュートリアルを進めるとそれらの中で ROS シミュレータなどのインストールも行います.

TORK MoveIt チュートリアル

ROS の入門には TORK MoveIt チュートリアルをお薦めします.MoveIt は ROS のマニピュレーションロボット動作計画ソフトウェアです.このチュートリアルでは数種のロボットの ROS シミュレータのインストールや基本的な操作,プログラムでのロボット操作を学習することができます.TORK MoveIt チュートリアルではプログラミング言語に Python を用いていますが,プログラミングの経験がほとんどない人にもプログラムによるロボット操作の体験と学習ができるように構成しています.

ROS を初めて使う方に TORK MoveIt チュートリアルを学習したときのレポートも下記の記事に書いてもらっています.学習過程でいろいろと疑問をもった点などの体験を書いてもらいましたので参考にしてみてください.

初めてのROS(ROSチュートリアルを使って)

 

ROS-Industrial トレーニング(日本語版)

より発展的な ROS プログラミングを学習したい場合は ROS-Industrial トレーニングを行ってみるのも良いでしょう.この教材で取り上げられているプログラミング言語は主に C++ と Python です.C++ によるロボット制御や画像処理,3D ポイントクラウド処理などとそれらの組み合わせのプログラムの学習ができます.

ROS-Industrialのトレーニング教材を日本語訳しました!

 

ROS で質問したいことが出てきたら

ROS Discourse やチュートリアル,パッケージの GitHub Issues に質問を投稿してみてください.

 

入門的な実機マニピュレーションロボット

1台のパソコンだけ,シミュレータだけでなく入門的な実機マニピュレータを利用してみたいと思った方は入門的なマニピュレーションロボット2例の導入検証を行った記事を参考にしてみてください.

ROS 入門向けマニピュレータ導入検証

著者:yamamoto.yosuke

ROS 入門向けマニピュレータ導入検証

ROS やその MoveIt の学習を始めたい,もしくは Gazebo などのシミュレータでの実行はできたので,実際のロボットも動かしてみたい!と思っている方もいらっしゃるのではないでしょうか.

また,Ubuntu ROS をインストールする PC はどのようなものにしたら良いのか?というご質問と同じように,マニピュレータを ROS で動かす学習をしたいが実際にどのようなロボットを導入したら良いのか?といったご質問を TORK にいただくことがあります.

そこで,価格なども含めて比較的入手性の良さそうな次の2種のマニピュレーションロボットを購入して ROS や MoveIt で利用した場合について調査・検証しました.

  • uArm Swift Pro
  • Open Manipulator X
uArm Swift Pro Open Manipulator X
販売価格 ¥99,000.- ¥272,800.-
納期 数日 2〜7週間
腕部自由度 3 DOF 4 DOF
PC 接続 micro USB-B micro USB-B
外観

 

各マニピュレータ ROS 対応情報

uArm Swift Pro

Open Manipulator X

ROS での導入方法

uArm Swift Pro

GitHub ページ SwiftAndProForROS https://github.com/uArm-Developer/RosForSwiftAndSwiftPro のトップページにある README.md に従ってダウンロードとインストールを行いました.

まず前提としてロボットに接続する側の PC に  Ubuntu と ROS がインストールされている必要があります.ROS のインストールは下記サイトを参考に行うことができます.

README.md にはおおまかにしか書いていないように思えたので,補足的に書き加えると次のようになります.

$ sudo apt-get install git ros-kinetic-serial
$ mkdir -p ~/catkin_ws/src  #既にワークスペースがあるならそちらを使ってもOK
$ cd ~/catkin_ws/src
$ catkin_init_workspace
$ git clone https://github.com/uArm-Developer/RosForSwiftAndSwiftPro.git
$ cd ~/catkin_ws
$ rosdep install --from-paths src --ignore-src -r -y
$ catkin_make
$ source ~/catkin_ws/devel/setup.bash

ここでは Ubuntu 16.04 + ROS kinetic のケースを書いていますが Ubuntu 18.04 + ROS melodic でも下記の kinetic のところを melodic にしてインストール・実行できました.

「 2. Set up enviroment 」は手順通りに ROS 環境がターミナルに反映されるための設定を行いました.ROS melodic の場合もインストールに関するコマンドの kinetic を melodic に変更することでインストールでき,今回の記事の範囲の動作を確認しました.

Open Manipulator X

<2022年8月31日追記>
本記事公開後に OpenManipulator-X のソフトウェア構成の変更があり,本記事で紹介した手順のアップデートが必要でしたので更新内容を新しい記事「“ROS 入門向けマニピュレータ導入検証” ROS Melodic & Noetic 対応更新」にて紹介しています.

eマニュアルが充実しているので下記 URL の手順に沿ってインストール作業を進めました.

こちらも ROS melodic の場合もインストールに関するコマンドの kinetic を melodic に変更することでインストールでき,今回の記事の範囲の動作を確認しています.

eマニュアルの手順に従い,Arduino IDE でポートの設定なども行いました.

MoveIt GUI でのマニピュレーション操作

uArm Swift Pro と Open Manipulator X を ROS の MoveIt の GUI(グラフィカル・ユーザ・インタフェース)から動かした手順を中心に報告します.

uArm Swift Pro

まずは uArm Swift Pro の電源投入と PC との接続を行います.

  1. uArm Swift Pro に ACアダプター電源を接続して電源を入れる
  2. USB ケーブルで uArm Swift Pro と Ubuntu PC を接続する

次にターミナルを2つ開いて,1つ目のターミナルでは uArm Swift Pro への接続と制御を実行します.

ターミナル1

$ sudo chmod 666 /dev/ttyACM0
$ roslaunch swiftpro pro_control.launch

2つ目のターミナルでは MoveIt を実行します.

ターミナル2

$ roslaunch pro_moveit_config demo.launch

腕自由度が 3 自由度しかないため MoveIt 上の空間の 6 自由度(XYZ, RPY)でインタラクティブマーカを動かそうとすると上手く動かせません.”Allow Approx IK Solutions” のチェックを入れるとロボットの自由度・可動範囲内でインタラクティブマーカの厳密ではないものの最適解が計算されるので比較的楽にインタラクティブマーカを動かすことができます.

  • Allow Approx IK Solutions のチェックを入れる

インタラクティブマーカを動かして目標姿勢を定めてから [ Plan and Execute ] ボタンを押します.

必須ではないですが MoveIt の表示上調整すると良かった項目を挙げます.

  • Displays → MotionPlanning
    • Planning Request → Interactive Marker Size : 0.1
    • Planned Path → Loop Animation : オフ

問題もありました.MoveIt に表示される uArm Swift Pro のロボットモデルがパラレルリンク分の運動学計算がされていないような状態と正常に計算されたような状態を交互に繰り返していました.この件は GitHub Issue – Missing robot joints としても報告されているようですが改善はされていないようです.

MoveIt やコントローラを終了するには各ターミナルで Ctrl+C を入力することで終了します.

uArm Swift Pro は動作時の剛性感が高いように思いました.ステッピングモータでガッチリと固定されているような印象を受けました.ただ MoveIt から制御した動作はカタカタカタとしていました.これはロボット側のファームウェアを更新したらカタカタの度合いが少し細かくなりましたがまだ残っています.uArm Swift Pro を uArm Studio から動かすと動きがスムーズだったので ROS や MoveIt と uArm Swift Pro のインタフェース部分に詰めきれていない部分があるように感じました.

Open Manipulator X

<2022年8月31日追記>
本記事公開後に OpenManipulator-X のソフトウェア構成の変更があり,本記事で紹介した手順のアップデートが必要でしたので更新内容を新しい記事「“ROS 入門向けマニピュレータ導入検証” ROS Melodic & Noetic 対応更新」にて紹介しています.

Gazebo Simulation

まずは Gazebo シミュレータが用意されているので Gazebo 上の Open Manipulator X を MoveIt から動かしてみました.

ターミナルを2つ開いて,1つ目のターミナルでは Open Manipulator X の Gazebo シミュレータを起動します.

ターミナル1

$ roslaunch open_manipulator_gazebo open_manipulator_gazebo.launch

正常に実行されると次の画像のような Gazebo のウィンドウが表示されます.ここで一番下段の部分にある ▶ ボタンをクリックしてシミュレータを走らせます.

次にコントローラと MoveIt を起動します.

ターミナル2

$ roslaunch open_manipulator_controller open_manipulator_controller.launch use_moveit:=true use_platform:=false

空間6自由度に対して腕部自由度が4自由度と少ないので Allow Approx IK Solutions のチェックを入れると楽にインタラクティブマーカを動かすことができます.

インタラクティブマーカを動かして目標姿勢を定めてから [ Plan and Execute ] ボタンを押します.

実機動作

次に Open Manipulator X の実機ロボットを MoveIt GUI から動かしてみました.

今回は Ubuntu PC と Open Manipulator X を OpenCR 回路を経由して接続しました.接続方法は下記ページに説明があります.

http://emanual.robotis.com/docs/en/platform/openmanipulator_x/ros_setup/#opencr

  1. Open Manipulator X から出ているケーブルを OpenCR ボードに差し込む
  2. AC アダプタからの直流電源を OpenCR ボードに接続
  3. USB ケーブルで Ubuntu PC と OpenCR ボードを接続
  4. OpenCR ボードの電源を入れる

ターミナルを1つ開いてコントローラと MoveIt を起動します.

ターミナル1

$ roslaunch open_manipulator_controller open_manipulator_controller.launch dynamixel_usb_port:=/dev/ttyACM0 use_moveit:=true

Gazebo シミュレーションのときと同様に “Allow Approx IK Solutions” のチェックを入れます.

インタラクティブマーカを動かして目標姿勢を定めてから [ Plan and Execute ] ボタンを押すと,Gazebo シミュレータで行ったときと同じように実機ロボットを操作できました.

MoveIt Commander でのマニピュレーション動作

MoveIt の GUI 経由で uArm Swift Pro と Open Manipulator X を操作することができました.次の段階としてプログラムから MoveIt を操作してロボットを動かしてみました.

GUI からではなくプログラムからロボットを操作できることで,例えば画像処理から得られた座標をもとににマニピュレータを動かすといった応用につながります.

プログラムから MoveIt を動かすには MoveIt Commander を利用します.MoveIt Commander には C++ や Python のインタフェースが用意されていますので,今回は Python にてプログラムを作成して各ロボットを動作させました.

uArm Swift Pro

MoveIt の GUI にあった “Allow Approx IK Solutions” にチェックを入れた場合と同様の動作指令を出せる MoveIt Commander の機能が set_joint_value_target() メソッドです.一般的には set_joint_value_target() メソッドには各関節の目標角度を引数として渡すことがまず説明されるかと思いますが,第1引数に Pose 型か PoseStamped 型のデータを第2引数に True (=近似解=Approximate / デフォルトは False =厳密解)を渡すことでマニピュレータの自由度が少ないことにより厳密解が得られない状態を近似解を用いることで回避します.

なお,6自由度以上を有するマニピュレータでは一般的に set_pose_target() に Pose 型か PoseStamped 型のデータを渡して厳密解をもって動作させますので,そのようなマニピュレータのプログラムを応用する場合には注意が必要です.

今回作成したテストプログラムを以下に記します.

uArm Swift Pro – MoveIt Commander テストプログラム

#!/usr/bin/env python

import sys, math, copy
import rospy, tf, geometry_msgs.msg

from moveit_commander import MoveGroupCommander, RobotCommander
from geometry_msgs.msg import Pose, PoseStamped

if __name__ == '__main__':
    
    node_name = "commander_example"
    rospy.init_node( node_name, anonymous=True )
    
    group = MoveGroupCommander("arm")
    
    group.set_planning_time( 600.0 )
    
    # Getting Initial Pose & RPY
    pose_init = group.get_current_pose()
    rospy.loginfo( "Get Initial Pose\n{}".format( pose_init ) )
    rpy_init  = group.get_current_rpy()
    rospy.loginfo( "Get Initial RPY:{}".format( rpy_init ) )
    
    # Pose 1
    rospy.loginfo( "Starting Pose 1")
    group.set_start_state_to_current_state()
    
    pose_target_1 = Pose()
    pose_target_1.position.x =  0.20
    pose_target_1.position.y =  0.00
    pose_target_1.position.z =  0.15
    pose_target_1.orientation.x =  0.0
    pose_target_1.orientation.y =  0.0
    pose_target_1.orientation.z =  0.0
    pose_target_1.orientation.w =  1.0
    
    group.set_joint_value_target( pose_target_1, True )
    group.go()
    
    rospy.sleep(5.0)
    pose_current = group.get_current_pose()
    rospy.loginfo( "Get Current Pose:\n{}\n".format( pose_current ) )
    
    # Pose 2
    rospy.loginfo( "Starting Pose 2" )
    
    pose_target_2 = Pose()
    pose_target_2.position.x =  0.15
    pose_target_2.position.y =  0.15
    pose_target_2.position.z =  0.10
    pose_target_2.orientation.x =  0.0
    pose_target_2.orientation.y =  0.0
    pose_target_2.orientation.z =  0.3826834
    pose_target_2.orientation.w =  0.9238795
    
    group.set_joint_value_target( pose_target_2, True )
    group.go()
    
    rospy.sleep(5.0)
    pose_current = group.get_current_pose()
    rospy.loginfo( "Get Current Pose:\n{}\n".format( pose_current ) )
    
    # Pose 2 Z:+0.05[m]
    rospy.loginfo( "Starting Pose 2 Z:+0.05[m]")
    pose_target_2.position.z += 0.05
    
    group.set_joint_value_target( pose_target_2, True )
    group.go()
    
    rospy.sleep(5.0)
    pose_current = group.get_current_pose()
    rospy.loginfo( "Get Current Pose:\n{}\n".format( pose_current ) )
    
    # Back to Initial Pose
    rospy.loginfo( "Back to Initial Pose")
    group.set_joint_value_target( pose_init, True )
    group.go()
    
    rospy.sleep(5.0)
    pose_current = group.get_current_pose()
    rospy.loginfo( "Get Current Pose:\n{}\n".format( pose_current ) )

基本的な流れとしては Pose 型のインスタンス pose_target_1 などに位置・姿勢のデータを代入して group.set_joint_value_target( pose_target_1, True )で目標をセットし,group.go() で実行しています.

プログラムの実行方法は前述の MoveIt GUI でロボットが動作する状態にしてからもう1つターミナルを開いてテストプログラムを実行します.今回はテストプログラムのファイルを ~/catkin_ws/src/pro_moveit_config/script/uarm-sp_moveit_tutorial_poses.py としましたので,次のようにターミナルで実行しました.

$ rosrun pro_moveit_config uarm-sp_moveit_tutorial_poses

MoveIt Commander プログラムで uArm Swift Pro を動作せたときの動画です.

Open Manipulator X

<2022年8月31日追記>
本記事公開後に OpenManipulator-X のソフトウェア構成の変更があり,本記事で紹介した手順のアップデートが必要でしたので更新内容を新しい記事「“ROS 入門向けマニピュレータ導入検証” ROS Melodic & Noetic 対応更新」にて紹介しています.

Open Manipulator X のテストプログラムも基本は uArm Swift Pro と同様に set_joint_value_target( Pose, True ) を利用して作成しました.

追加的に Open Manipulator X の運動学上の「厳密解」の位置・姿勢データを予め計算しておいて set_pose_target() に与えたときの動作の様子もテストしました.

今回作成したテストプログラムを以下に記します.

Open Manipulator X – MoveIt Commander テストプログラム

#!/usr/bin/env python

import sys, math, copy
import rospy, tf, geometry_msgs.msg

from moveit_commander import MoveGroupCommander, RobotCommander
from geometry_msgs.msg import Pose, PoseStamped

if __name__ == '__main__':
    
    node_name = "commander_example"
    rospy.init_node( node_name, anonymous=True )
    
    group = MoveGroupCommander("arm")
    
    group.set_planning_time( 600.0 )
    
    # Getting Initial Pose & RPY
    pose_init = group.get_current_pose()
    rospy.loginfo( "Get Initial Pose\n{}".format( pose_init ) )
    rpy_init  = group.get_current_rpy()
    rospy.loginfo( "Get Initial RPY:{}".format( rpy_init ) )
    
    # Pose 1
    rospy.loginfo( "Starting Pose 1")
    pose_target_1 =  [ 0.12, 0.0, 0.1, 0.0, math.pi/2.0, 0.0 ] # [ x, y, z, r, p, y ]
    group.set_pose_target( pose_target_1 )
    group.go()
    
    rospy.sleep(5.0)
    pose_current = group.get_current_pose()
    rospy.loginfo( "Get Current Pose:\n{}\n".format( pose_current ) )
    
    # Pose 2
    rospy.loginfo( "Starting Pose 2")
    group.set_pose_target( [ 0.2, 0.0, 0.2, 0.0, 0.0, 0.0 ] )
    group.go()
    
    rospy.sleep(5.0)
    pose_current = group.get_current_pose()
    rospy.loginfo( "Get Current Pose:\n{}\n".format( pose_current ) )
    
    # Pose 3
    rospy.loginfo( "Starting Pose 3")
    
    pose_target_3 = Pose()
    pose_target_3.position.x =  0.10
    pose_target_3.position.y =  0.10
    pose_target_3.position.z =  0.10
    pose_target_3.orientation.x = -0.2706
    pose_target_3.orientation.y =  0.6533
    pose_target_3.orientation.z =  0.2706
    pose_target_3.orientation.w =  0.6533
    
    group.set_joint_value_target( pose_target_3, True )
    group.go()
    
    rospy.sleep(5.0)
    pose_current = group.get_current_pose()
    rospy.loginfo( "Get Current Pose:\n{}\n".format( pose_current ) )
    
    # Pose 3 Z:-0.05[m]
    rospy.loginfo( "Starting Pose 3 Z:-0.05[m]")
    pose_target_3.position.z -= 0.05
    
    group.set_joint_value_target( pose_target_3, True )
    group.go()
    
    rospy.sleep(5.0)
    pose_current = group.get_current_pose()
    rospy.loginfo( "Get Current Pose:\n{}\n".format( pose_current ) )
    
    # Pose 4
    rospy.loginfo( "Starting Pose 4")
    
    pose_target_4 = Pose()
    pose_target_4.position.x =  0.10
    pose_target_4.position.y = -0.10
    pose_target_4.position.z =  0.05
    pose_target_4.orientation.x =  0.2706
    pose_target_4.orientation.y =  0.6533
    pose_target_4.orientation.z = -0.2706
    pose_target_4.orientation.w =  0.6533
    
    group.set_joint_value_target( pose_target_4, True )
    group.go()
    
    rospy.sleep(5.0)
    pose_current = group.get_current_pose()
    rospy.loginfo( "Get Current Pose:\n{}\n".format( pose_current ) )
    
    # Pose 4 Z:+0.05[m]
    rospy.loginfo( "Starting Pose 4 Z:+0.05[m]")
    pose_target_4.position.z += 0.05
    
    group.set_joint_value_target( pose_target_4, True )
    group.go()
    
    rospy.sleep(5.0)
    pose_current = group.get_current_pose()
    rospy.loginfo( "Get Current Pose:\n{}\n".format( pose_current ) )
    
    # Back to Initial Pose
    rospy.loginfo( "Back to Initial Pose")
    group.set_joint_value_target( pose_init, True )
    group.go()
    
    rospy.sleep(5.0)
    pose_current = group.get_current_pose()
    rospy.loginfo( "Get Current Pose:\n{}\n".format( pose_current ) )

このテストプログラムのうち # Pose 1 と # Pose 2 の部分は set_pose_target() で目標姿勢を設定しています.このようにXZ平面上に位置する点の厳密解を指定した場合は set_pose_target() でも動作しましたが,# Pose 3 や # Pose 4 のようにXZ平面から外れたところは set_joint_value_target( Pose, True ) を利用しないと動作しませんでした.

プログラムの実行方法は前述の MoveIt GUI でロボットが動作する状態にしてからもう1つターミナルを開いてテストプログラムを実行します.今回はテストプログラムのファイルを ~/catkin_ws/src/pro_moveit_config/script/openmanipulatorx_moveit_tutorial_poses.py としましたので,次のようにターミナルで実行しました.

$ rosrun open_manipulator_moveit openmanipulatorx_moveit_tutorial_poses.py

Open Manipulator X を MoveIt Commander から動作させたときの動画です.

まとめ

今回 ROS の入門向けを念頭に2つのマニピュレータを導入し調査・検証しました.その結果を以下にまとめます.

  • 自由度が 6DOF 未満であることによる影響
    • MoveIt (GUI) では  “Allow Approx IK Solutions” のチェックを入れる必要あり
    • MoveIt Commander では目標姿勢のセットに set_joint_value_target() を使う必要あり
  • uArm Swift Pro に関して
    • ROS・MoveIt 対応パッケージの更新は滞っているように思える
      • MoveIt モデルの不備
      • MoveIt からの動作がカタカタする(遅い制御周期?)
    • ユーザ対応やユーザ間交流は ROS も含めてフォーラムで行われているよう
    • ロボット-PC 間が USB ケーブル1本で接続ができ簡単
    • Open Manipulator X に比べたら安価
  • Open Manipulator X に関して
    • eマニュアルが充実
    • Gazebo シミュレータあり
    • グリッパ付属
    • ロボット-PC 間に OpenCR ボードを挟み,そのポート設定も必要
      • OpenCR を使いこなせば拡張性が高い
    • uArm Swift Pro に比べたら高価

 総評

今回の2台のマニピュレータであれば Open Manipulator X の方が提供されている情報も多く,予算的に許されるのであれば入門に適していると思いました.

 

著者:yamamoto.yosuke

ROS 導入ノートパソコン比較調査

TORK の ROS ワークショップを受講された方などから ROS を使用するにあたりどのような PC を利用したら良いかを問い合わせいただくことがあります.

基本的には利用したい ROS の各バージョンに対応した Ubuntu のバージョンが動作可能な PC であれば良いのですが,スペックが多岐にわたるパソコンの数々からどのようなパソコンを選んだら良いのか迷ってしまいます.

  • ROS Kinetic → Ubuntu 16.04 が動作可能な PC
  • ROS Melodic → Ubuntu 18.04 が動作可能な PC

そこで ROS を導入するパソコンの選定の参考なるよう,4つの異なる特徴のノートパソコンに実際に Ubuntu と ROS を導入して,その導入のポイントや動作結果を報告したいと思います.

  1. 10万円未満 モバイルノートパソコン : Dell Inspiron 13 5390
  2. 軽量モバイルノートパソコン : Dell XPS 13 7390
  3. モバイルワークステーション : Lenovo ThinkPad X1 Extreme 2nd Gen
  4. 2018年モデルモバイルノートパソコン : Lenovo ThinkPad T480s (既存品)

各ノートパソコン主要スペック

各ノートパソコンの主要なスペックは以下のとおりです.

Inspiron 13 5390 XPS 13 7390 ThinkPad X1 Extreme 2nd ThinkPad T480s
CPU Core i5-8265U Core i7-10710U Core i9-9880H Core i7-8550U
Threads / Cores 8 / 4 12 / 6 16 / 8  8/ 4
iGPU Intel UHD 620 Intel UHD 630 Intel UHD 630 Intel UHD 620
dGPU NVIDIA GeForce GTX 1650 Max-Q NVIDIA GeForce MX150
メモリ 8 GB 16 GB 32 GB 16 GB
SSD 256GB 512 GB 512GB + 1.0 TB 512 GB
ディスプレイサイズ 13.3″ IPS 13.3″ IPS 15.6″ IPS 13.3″ IPS
ディスプレイ解像度 1920 x 1080 3840 x 2160 (4K) 1920 x 1080 1920 x 1080
ディスプレイその他 グレア グレア・タッチ ノングレア ノングレア・タッチ
WiFi チップ Intel AC 9462 Intel AX200 Intel AX200 Intel AC 8265
電源 独自規格 USB-C 独自規格 USB-C
販売年 2019 2019 2019 2018
購入先 Amazon.co.jp Dell Lenovo
税込価格 ¥89,609.- ¥235,499.- ¥400,928.-
納期 即納 即納 約3週間
外観

Ubuntu Certified hardware

ROS / Ubuntu の導入機種を選ぶにあたって Ubuntu Certified hardware という Web ページが参考になります.

Ubuntu Certified hardware で今回の各ノートパソコンの対応状況を調べた結果が次の通りです.

これらのノートパソコンに限れば,2018 年の日付があるものは Ubuntu 16.04 に対応していて,2019 年の日付があるものは Ubuntu 18.04 に対応していると記載されています.

次項目で記述しますが対応が非明記のバージョンの Ubuntu をインストールしても問題なく動作する組み合わせもあります. Ubuntu Cetrified hardware を参考にしつつ,各ノートパソコンで使用されている各種チップの Linux デバイスドライバの対応状況等ふまえて導入を検討するのが良さそうです.

Ubuntu のインストール手順

Ubuntu はバージョン 16.04 と 18.04 をそれぞれのノートパソコンにインストールを試みました.各ノートパソコンに購入時にインストールされている Windows 10 を残したまま Ubuntu も起動できるように SSD にパーティションを切ってインストールすることとしました.

Ubuntu のインストール手順は Dell と Lenovo のノートパソコンで BIOS の設定方法が少し違うので分けて説明したいと思います.

なお,本記事では必要な手順の項目を中心にお伝えします.実際に PC に Ubuntu をインストールする際には具体的な方法を十分調査の上作業を行ってください.

Dell ノートPCへの Ubuntu のインストール手順

後述する Lenovo ThinkPad への Ubuntu のインストールと比べて BIOS の設定変更に関する手順が多くなっています.

  • Windows で記憶デバイスのパーティションを切って Ubuntu をインストールするディスク領域を確保
  • BIOS(UEFI) の設定変更
    • Secure Boot を OFF にする = USB メモリからのブートを可能にする
    • SATA Operation を RAID から ACHI モードに変更 = Linux ディスクにアクセスできるようにする
    • RAID モード(デフォルト)の状態で Windows を通常起動
    • 管理者権限でコマンドプロンプトを起動しbcdedit /set {current} safeboot minimal を実行
    • PC の再起動
    • PC 再起動時の Dell ロゴ画面にて F2 を押して BIOS(UEFI) 設定に入る
    • System Configuration の SATA Operation を RAID から ACHI に変更
    • APPLY CHANGES で設定変更を反映させてから EXIT にて再起動
    • Windows 10 のセーフモードの起動
    • 管理者権限でコマンドプロンプトを起動して bcdedit /deletevalue {current} safeboot を実行
  • Ubuntu Linux のインストール
    • Ubuntu インストーラの入っている USB メモリを PC に接続
    • PC の起動
    • PC 再起動時の Dell ロゴ画面にて F12 を押して One-Time Boot Settings に入る
    • Ubuntu インストーラの入っている USB メモリを選択して起動
    • Ubuntu のインストールの実行
  • デバイスドライバのアップデート・インストール
    • インターネットに接続した状態で Ubuntu のソフトウェアのアップデートを行う
    • XPX 13 7390
      • Ubuntu 18.04 には Dell からデバイスドライバが用意されているのでダウンロードしてインストールする
      • Ubuntu 16.04 にはデバイスドライが対応していない
        • ディスプレイのスケーリングができない = 文字が小さすぎて見えない
        • WiFi チップの Intel AX200 がドライバ対応されていない
          • USB 接続の有線 Ethenet なら利用可能

インストールにあたっては次のサイトを参考にしました.

Lenovo ThinkPad ノートPCへの Ubuntu のインストール手順

  • Windows で記憶デバイスのパーティションを切って Ubuntu をインストールするディスク領域を確保
  • BIOS の設定変更
    • 起動時 Lenovo ロゴ画面で Enter を押した後にメニューに従って F1 で BIOS Setup Utility に入る
    • Security Chip の無効化
    • Secure Boot の無効化
  • Ubuntu のインストール
    • Ubuntu インストーラの入っている USB メモリを PC に接続
    • PC の起動
    • 起動時 Lenovo ロゴ画面で Enter を押したあとにメニューに従って F12 で choose a temporary startup device を選択
    • Ubuntu インストーラの入っている USB メモリを選択して起動
    • Ubuntu のインストールの実行
  • デバイスドライバのアップデート・インストール
    • インターネットに接続した状態で Ubuntu のソフトウェアのアップデートを行う
    • NVIDIA GeForce ドライバの適用
      • グラフィックドライバのリポジトリの追加
        • sudo add-apt-repository ppa:graphics-drivers/ppa
        • sudo apt-get update
      • Software Updater → Settings … → Additional Drivers → nvidia-driver-440 → Apply changes
    • ThinkPad X1 Extreme のみ
      • WiFi チップの Intel AX200 がドライバ対応されていない
        • USB 接続の有線 Ethenet なら利用可能

Dell と Lenovo のノートPCインストール手順の比較・総評

  • Dell に比べて Lenovo ThinkPad の方が BIOS 関連の設定変更が楽
    • Dell の SATA Operation を RAID から ACHI モードに変更する手順が複雑
  • Ubuntu インストール後アップデート作業までは…
    • WiFi 接続ができないので USB 接続有線 Ethernet アダプタが必要
    • タッチパッドが機能しない可能性があるので USB 接続のマウスが必要
  • 13インチ 4K ディスプレイはスケーリングが適用されるまでは文字が小さくて非常に見づらい
  • Ubuntu 16.04 においては WiFi チップ Intel AX200 のドライバを適用しても WiFi に接続しようとすると OS がフリーズした
  • 最新のチップ構成だと Linux ドライバが対応していない可能性に注意
  • Ubuntu 18.04 は今回試したいずれの PC でも正常に動作可能と言える
  • Ubuntu Certified hardware で各 PC の Ubuntu バージョンの対応関係は参考になる

ベンチマークテスト

CPU ベンチマーク比較

各 PC で UnixBench を実行して,シングルコア・マルチコアのスコアを調査しました.

Dell Inspiron 13 5390 Dell XPS 13 7390 ThinkPad X1 Extreme Gen2 ThinkPad T480s
スペック
CPU ナンバー Core i5-8265U Core i7-10710U Core i9-9880H Core i7-8550U
スレッド / コア 8 / 4 12 / 6 16 / 8 8 / 4
ベース周波数 1.6 GHz 1.1 GHz 2.3 GHz 1.8 GHz
最大周波数 3.9 GHz 4.7 GHz 4.8 GHz 4.0 GHz
UnixBench
シングルコア 1640.1 2106.8 1829.1 1234.6
マルチコア 3550.8 6012.3 7960.4 3399.5

Multi Cores はコア数に応じたスコアを示しているように思います.

また Dell Inspiron 13 5390 が値段の割に良い結果が得られました.

GPU ベンチマーク比較

GPU は ROS では Gazebo シミュレーションの 3D 表示能力などと関係がある項目です.

各 PC で Unigine Benchmark – Heaven (1920 x 1080) と glmark2 をそれぞれ実行して,スコアを調査しました.

Dell Inspiron 13 5390 Dell XPS 13 7390 ThinkPad X1 Extreme Gen2 ThinkPad T480s
GPU Intel UHD 620 Intel UHD 630 Intel UHD 630 GeForce GTX 1650 Intel UHD 620 GeForce MX150
Unigine Benchmark – Heaven 1920 x 1080
Score 281 310 290 1565 267 548
FPS 11.2 12.3 11.5 62.1 10.6 21.8
Min FPS 5.0 5.9 8.1 21.4 5.3 6.3
Max FPS 23.7 25.9 23.2 124.7 22.1 46.2
glmark2
Score 2244 2892 2978 2633 2500 3343

Unigine Benchmark – Heaven では NVIDIA グラフィックの優位性が顕著に出ました.

一方 glmark2 の方は NVIDIA GeForce MX150 の優位性はあるものの NVIDIA GeForce GTX 1650 はむしろ Intel UHD 630 よりも低いスコアとなりました.これらの PC では glmark2 実行時に 2000 〜 4000 fps ほど出てしまうので,現在の GPU に対しては負荷が軽すぎるような印象を持ちました.

ROS PCL プロセス処理周波数比較

実際の ROS プロセスの動作状況を比較するためにポイントクラウドのフィルタリング処理能力を比較しました.

予め記録したポイントクラウドメッセージの rosbag データを各 PC で Voxel Grid フィルタと Statistical Outlier Removal フィルタをかけて ROS トピック /camera/statistical_outlier_removal/output として出力して周波数を調査しました.

$ rostopic hz /camera/statistical_outlier_removal/output
Dell Inspiron 13 5390 Dell XPS 13 7390 ThinkPad X1 Extreme Gen2 ThinkPad T480s
MAX 2.855 5.052 4.333 2.000
Average 2.353 3.759 3.830 1.873
min 2.126 2.975 3.623 1.787

3D グラフィクの表示は行わなかったので基本的には CPU によるデータ処理と考えられます.

CPU Multi Cores のスコアが一番近い傾向にあるように思いますが,Dell XPS 13 と ThinkPad X1 Extreme Gen2 の差は CPU Multi Cores ほどは出ませんでした.より多い数のスレッドを必要とするプロセスでは差が出るかもしれません.また Dell XPS 13 7390 は処理周波数の最大,最小の差が大きかったです.

Dell Inspiron 13 5390 は値段の割に良い結果を出している印象を持ちました.

まとめ

  • インストール・設定関係
    •  BIOS
      • Lenovo ThinkPad の方が Dell に比べて BIOS 関連の設定変更が楽
    • 13インチ 4K ディスプレイ(今回は Dell XPS 13 7390)
      • スケーリングが適用できるようになるまでは文字が小さくて非常に見づらい
    • Ubuntu 18.04 は今回試したいずれの PC でも正常動作
    • Ubuntu 16.04 においては一部 WiFi チップ Intel AX200 のドライバに不具合
    • 最新のチップ構成だと Linux ドライバが対応していない可能性に注意
    • Ubuntu Cetrified hardware で各 PC の Ubuntu バージョンの対応関係は参考になる
  • 各ノートパソコン性能など
    • Dell Inspiron 13 5390
      • 10万円未満という値段の割には良い性能
        • 低コストやドライバ対応の面を考えると Ubuntu + ROS 入門用に向いている
          • ただ BIOS の設定に手間がかかる
    • Dell XPS 13 7390
      • 今回の ROS ポイントクラウド処理では ThinkPad X1 Extreme に肉薄する性能
    • ThinkPad X1 Extreme Gen2
      • CPU・GPU・ROS PCL の全ベンチマークで最高性能を発揮
    • ThinkPad T480s
      • Intel GPU に比べて NVIDIA GPU の優位性あり
      • BIOS 設定も簡単で基本的なドライバは追加する必要もなくインストールが楽
  • その他
    • グレアディスプレイだと写真や動画を撮るときに反射・映り込みに配慮する必要あり
Dell Inspiron 13 5390 Dell XPS 13 7390 ThinkPad X1 Extreme Gen2 ThinkPad T480s
Ubuntu 16.04 + ROS Kinetic
Ubuntu 18.04 + ROS Melodic
BIOS 設定の容易さ
ROS ポイントクラウド処理性能
価格
著者:yamamoto.yosuke

CIS ToF カメラセンサ がネット購入できるようになりました

2019年に ROS パッケージをリリースしました CIS ToF カメラセンサ DCC-RGBD1 がネットから購入できるようになりました.

Amazon.co.jp で購入の場合は日本国内への出荷のみですが,日本国外へも 株式会社シーアイエス の販売窓口メールアドレス ec-sales@ciscorp.co.jp にお問い合わせいただくと販売可能とのことです.

著者:yamamoto.yosuke

CIS ToF カメラセンサの ROS ドライバパッケージをリリースしました

新しい ROS パッケージ cis_camerahttps://github.com/tork-a/cis_camera )をリリースしました.

この ROS パッケージは 株式会社シーアイエスhttps://www.ciscorp.co.jp/ ) ToF (Time of Flight) カメラセンサ DCC-RGBD1 のためのドライバパッケージです.

DCC-RGBD1 は小型ながら広いレンジの深度画像が取得可能な ToF カメラセンサ(ディベロップメントキット)です.

  • 15cm 〜 5m のレンジで高精度な深度画像を取得可能
  • 小型 H:50mm × W:55mm × D:35mm(突起部を含まず)
  • RGB (QVGA) と Depth / IR (VGA) の3つの画像を同時取得
  • インタフェースは USB 3.0( USB 3.0 micro B コネクタ搭載:USB 給電は非対応 )
  • 屋内使用向け

本パッケージでは CIS ToF カメラセンサの ROS ドライバに加え,ノイズ除去,平面検出・除去,対象物点群抽出とフレーム座標算出のポイントクラウド処理ならびに,それらの処理結果を RViz で 3D 表示するためのサンプルプログラムおよび launch ファイルを同梱しています.

使い方は GitHub のドキュメントをご参照ください.
もし問題にぶつかった場合は GitHub Issues で報告をお願いします.

CIS ToF カメラセンサのハードウェアの入手などに関するお問い合わせは下記連絡先までお願いします.

ハードウェアに関するお問合せ先:株式会社シーアイエス 営業担当
メールアドレス:newbiz@ciscorp.co.jp
電話番号:042-664-5568