Adventurer3コントローラーで作成したGコード編集機能を、切り出して、単独で利用できるように公開した。
ソースコードは、Githubから。
概要
プログラムはC#で作成しており、全体の構成は次のようになっている。
- rs274ngcparser
Gコードファイルの構造を解析する、基本的なライブラリ - Translator
Adventurer3コントローラーで使用している、FlashPrint, Simplify3D, Slic3rで出力したGコードファイルに対して、各種修正を行い、Adventurer3に読み込ませることが可能なような形式に変換するためのライブラリ - TranlatorToAdventurer3
Translatorを使用し、Adevneture3コントローラーのGコードファイル変更をコマンドプロンプトで使用できるようにしたバッチプログラム - CalcGcode
rs274ngcparserを使用し、データを集計する機能を実装した、サンプルプログラム
rs274ngcparser
Gコードを解析するための基本的なライブラリ。
ParseGCodeStreamが解析のためのベースとなるクラスで、実際に解析を行うのはICommandActorインターフェースを実装したクラスになる。
LineCommandは、ファイルから1行読み込み、その行にあるGコードの情報を解析しした結果を保存したもので、ICommandActorではLineCommandをもらい受け、各種処理を行うようにしている。
処理の基本的な流れ
ParseGCodeStream
解析のためのベースとなるクラスで、Parseメソッドを主に利用する。
ParseGCodeStream内では読み込んだファイルから1行読み込み、その1行の中にある、コメントやXYZGなどのコード類を解析し、LineCommandを構築する。その構築したLineCommandをICommandActorに渡している。
読み込み可能なファイルはASCIIテキストファイル。ただしFlashPrintはファイルの先頭にバイナリ情報が書き込まれているので、それをそのまま通すと正しく読み込むことができない。
そこで、SkipXGcodeというメソッドを用意しているので、事前にそれを通すことで、先頭のバイナリ情報をスキップして、処理を続行することができる。
そのため、実際のコードは次のようになる。
var actor = new CalcLength(); // これはICommandActor派生
var modifier = new ParseGCodeStream();
ParseGCodeStream.SkipXGcode(stream);
modifier.Parse(stream, actor);
LineCommand
Gコードファイルの1行を解析し、利用しやすいようにした情報。
;から始まるコメントは、Commentプロパティに保存され、X??.?? Y??.??などのフィールド情報は、1文字目の識別子とその後の数値に分解した組を保存しておく。
識別子と数値は、ValuePositionクラスで管理し、保持している情報は、識別子(Key)、値(Value)、フィールドがどの位置にあったか(Position)、浮動小数点桁数(FP)を保持している。
Modifyメソッドを使うことで、ValuePositionデータの更新を行うこともできる。
ICommandActor
ParseGCodeStreamから呼び出される、Gコードについての処理を記述するためのインターフェースクラス。
実際の処理は、このインターフェースを継承したクラスを用意し、その中に記述する。
書き方などについては、CalcGcodeを一部参考にしてもらえれば。CalcGcodeの説明については、別途記述する。
rs274ngcparser
ユーティリティ的なクラスについて
ValueXYZEF
Gコードの中でよく利用される距離に関係する、X, Y, Z, E, Fに関して、情報を保持している。Fは若干違うが。
一応、それぞれの値は次のような意味を持っている。
- X, Y, Z
それぞれの軸に関しての絶対座標値
ValueXYZEF内での単位はmmで取り扱っている。 - E
フィラメントの送り出し量
ValueXYZEF内での単位はmmで取り扱っている。 - F
移動時のスピードに関しての情報
ValueXYZEF内での単位はmm/分で取り扱っている。
Gコードでは、これら座標値の単位・取扱いはG91, G90, G20, G21で変わってくる。
G90/91が指定されていると、それ以降の座標値は、それぞれ相対座標・絶対座標で示されることを表す。
G20/21がが指定されていると、それ以降の座標値は、それぞれインチ・mmで示されることを表す。
ValueXYZEF内では、G90/91, G20/21を解釈し、G1が指定されたときに、X, Y, Z, E, Fをmmの絶対値になるよう変換し保持している。
自分自身のICommandActor派生クラス内では、ValueXYZEFを保持し、PreAction(), ActionLine()で呼び出すことで、利用するのがいい。
例えば以下のような感じ。
class HogeHoge: ICommandActor {
ValueXYZEF current_ = new ValueXYZEF();
public bool ActionLine(LineCommand line) {
if (current_.ActionLine(line)) {
// 独自処理
return true;
}
return false;
}
public void PostAction() {
current_.PostAction();
}
public void PreAction() {
current_.PreAction();
}
}
OutputGCode
修正したGコードの情報をファイルに出力するためのメソッド類を保持するクラス。
ToString()にて、LineCommandもしくはValuePositionの情報を文字列化する。
例えば、以下の様に使うことで、読み込んだ行とほぼ同じ内容のものをコンソールに出力することができる。
public bool ActionLine(LineCommand line) {
Console.WriteLine(OutputGCode.ToString(line));
return true;
}
座標値に関しては、ValuePositionの小数点桁数も利用している。そのため、特に何も指定せずとも、例えばX1.00と記述されたものをX1.00と出力することが可能になっている。
OutputGCodeはLineCommand.Modifyで編集した結果を出力するようなケースで使うのが多いと思う。
例えば、上記コードに対して、さらに情報を修正するコードを入れてみると。
public bool ActionLine(LineCommand line) {
line.Modify('Z', (x) => x.Value += offsetZ);
Console.WriteLine(OutputGCode.ToString(line));
return true;
}
これで、Z座標に対してオフセットした結果を出力するというコマンドになる。
Translator
Adventurer3用のGコードファイルに変換するためのクラスを保持するライブラリ。
処理内容は、Adventurer3コントローラーを参照。
ToAdventurer3
Simlify3D、FlashPrintから生成されたファイルとSlic3rから変換されたファイルに対して修正をして、Adventurer3用のGコードファイルを作成するクラス。
変換時に使用するパラメータはToAdventurer3Parameterで保持し、一応、シリアライズ対象にしている。
ToAdventurer3Parameter
パラメータ類は、プロパティとして用意している。
デシリアライズ時、設定されているパラメータのチェックはCheckメソッドで実施している。このチェックの結果は、Dictionary<string, string>で返却される。キーがパラメータのプロパティ名で、設定内容がエラーメッセージになっている。
json形式でシリアライズした場合、以下のような形式で出力される。
{
"EnclosureFanOn":false,
"MotorA":100,
"MotorB":20,
"MotorX":100,
"MotorY":100,
"MotorZ":40,
"PlayRemovalLength":0.5,
"OffsetZ":-0.02,
"BrimSpeedTypeValue":1,
"BrimSpeed":600,
"BrimSpeedRatio":100,
"BrimExtrudeRatio":150
}
BrimSpeedTypeValueはブリムのスピード設定方法で、0,1,2がそれぞれ、変更しない、絶対値で変更する、ファイル中で出力されたデータの割合で変更するに相当する選択値になっている。
Simplify3DParameter
これは、内部で使用しているクラス。Simplify3Dのファイルを読み込んだ時にファイルのヘッダー側に設定されているスライス用パラメータの情報を読み込んで保持しておくためのもの。
Adventurer3用のデータ変換時に、XY軸移動スピード、Z軸移動スピードを使用するために、覚えておくようにしている。
ToAdventurer3
Adventurer3用のデータ変換本体。
Modifyメソッド内で、自前でParseGCodeStreamを用意し変換処理をしている。事前にファイルの種別を判断したのち、ParseGCodeStream.Parseを実施している。
実際のGコード編集処理は、ActionLineからの呼び出しから実施している。
Slic3rToBase
Scli3rのファイル中のZ軸移動速度の修正を行いToAdventurer3に渡せるようにデータを整える処理を行っている。
またSlic3rToBaseを直接シリアライズ可能としており、動作パラメータもこのクラス内で保持している。これは、パラメータの数が1個しかなかったため、あえて別のクラスに分離しなかった。
こちらも、ToAdventurer3と同様にModifyメソッド内で、自前でParseGCodeStreamを用意し変換処理をしている。事前に、ToAdventurer3に渡すためのパラメータを読み込むため。
Scli3rで出力されたファイルのスライス用パラメータは、Simplify3Dとは違い、フッター型にあるため、事前にそこまで読み込み、動作パラメータを取得したのち、ParseGCodeStream.Parseを実施している。
実際のGコード編集処理は、ActionLineからの呼び出しから実施している。
ちなみにjson形式でシリアライズした場合、以下のような形式で出力される。
{
"SpeedZ":30
}
一応、キーワードが違うので、ToAdventurer3用のパラメータに混ぜて、1ファイルでの運用も可能としている。
TranlatorToAdventurer3
概要
TranslatorライブラリのToAdventurer3とSlic3rToBaseを利用した、GコードファイルをAdventurer3用に変換するバッチプログラム。
入力するファイルは、次の2つ。
- FlashPrint, Simplify3D, Slic3rで作成されたGコードファイル。
ただし、入力可能なファイルの拡張子は、gxとgに固定している。 - 動作パラメータを保存した、拡張子がjsonのファイル。
動作パラメータはGコードパーサー-3で、示したToAdventurer3とSlic3rToBaseで読み込み可能なパラメータをまとめたもの。
出力するファイルは、データ編集後のファイルで、拡張子をgoutに変更して出力する。
内部動作
以下の様な動作になっている。
- 入力パラメータ(引数で指定されたファイル名)の解析
拡張子がgx, gだったら、Gコードファイルとして処理し、拡張子がjsonの場合パラメータファイルとして処理する。 - Gコードファイルを事前読み込みし、ファイルがFlashPrint, Simplify3D, Slic3rのどのスライサーで作成されたかを認識する。
認識の方法は、ファイル先頭のコメントを読み込んで判断する。 - Slic3rで作成されたファイルの場合は、Slic3rToBaseで事前処理する。
この処理で、ToAdventurer3で、処理可能なファイルに変更される。 - ToAdventurer3でAdventurer3用ファイル形式に変換する。
CalcGcode
概要
これは、rs274ngcparserライブラリを使用したサンプルプログラムで、次の内容のものを実装している。
- ノズルの移動量の合計。
吐出していないときの移動とZ軸の移動も含めた移動量を算出している。 - ノズルの移動時間の合計。
移動量と移動速度(Fで指定された情報)をもとに時間を求め、その合計値を出したもの。 - フィラメントの吐出量の合計。
ノズルの移動時間と、実際の出力時間の差について
このプログラムで計算した結果は、どちらかというと、各スライサーで計算された時間に近い値が出力されている。
ということは、スライサーでの時間に関しての計算アルゴリズムは、このプログラムと同じということになる。
実際の出力時間は、ここで出力された時間より長い結果になるのが多い。
たぶん、以下の理由によるものと思われる。
- ノズル・ベッドの温度設定時間は考慮されていない
- ヘッドがベッドの上に来るまでの時間が計算できない
G161コマンドでXY原点位置への移動とか、初めのZ軸移動に関しては、現在のノズルの位置や移動先の座標がプログラム側には不明となってしまうため、計算できない。 - 指定された移動速度と実際の移動速度との差が盛り込まれていない。
たぶんGコードで指定された速度に比べ、実際のノズルの移動速度は遅いのだと思われる。移動時のトップスピードはFで指定されたものだが、始終点付近は、加減速のため若干遅くなるのではないか。
結果、計算された出力時間に比べ、実際の出力時間が多くなるのだと思う。
内部動作
フィラメントの吐出量の合計
G92コマンドで、Eの値のリセットが行われるため、G92でEが0になった場合、その前までのEの値をストックしておき、postActionで合計値を算出している。
ノズルの移動量の合計
G1コマンドがある場合、XYZがどれか移動されていると判断し、前回のXYZの値から、今回の位置までの距離を算出し、合計値を出している。
ノズルの移動時間の合計
ノズルの移動量を求めた後、その時のFの値で移動時間を算出し、その合計値を求めている。
rs274ngcparserTests/TranslatorTests
ライブラリの単体テスト用のプロジェクト
最後に
今回作ったパーサーは、rs274ngcの仕様はきちんとみず、過去の経験(rs-274xという仕様でプログラムを作ったことがある)と、G-code – RepRapを参考にして作ってしまっている。
そのため、rs274ngcの仕様的には問題がある個所があるかもしれないが、それはその時にでも考えよう。
ちなみに、rs274ngcの解釈する処理自体は、結構昔からあり、NISTが出している。
興味がある方はそちらを参照。