2016年6月22日水曜日

SmartLaser用のアプリを0から開発する。その7 とりあえずの目標とか方針とか。


まぁ、気分でつくってはいるものの、とりあえずの方針というか、予定を考えてみます。
(1)SmartlaserCO2の基本的なコマンドの確認 → OK
(2)仮想COMポートに対するアクセスと、SmartLaserCo2の初期化等を確認 → OK
(3)強さ、SPEEDの設定、レーザーで線を引く → OK
(4)画像をスキャンして線の集合でレーザーする → OK
----ここまで終了----
(5)画像をラスタ出力する
(6)SVGでパスを読み込む
(7)SVGで画像を読み込む
(8)SVGのパスで線をレーザーする
(9)SVGのパスで閉領域をレーザーする
(10)SVGの画像でレーザする
(11)アプリの形にする
----このあと機能拡張----
候補
・SVGのパスの線巾を考慮した複線化出力をする

SmartLaser用のアプリを0から開発する。その6【画像をダイレクトにベクタ出力する件】

では、画像をスキャンしてレーザーへ出力してみます。
レーザーはラスタ出力モードとベクタ出力モードがあるようですが、
今回は、画像をわざわざというか、ベクタのラインの集合で出力してみます。
なんかこっちの方がキレイにできそうな気がしたからです。
あと、閉領域の出力のテストを兼ねてってことにもなります。

とりあえず画像はモノクロ2値とし、テストで出力するまでがこんな感じです。
(1)まず最初STEP値を決める。解像度の逆数みたいなもの。
(2)次に解像度に合わせてイメージのビットマップを拡縮をかける
(3)1行ずつ、ビットフィールドを始点-終点に変換した座標のarrayを作成
(4)そのarrayで線の数だけLOOP。
※全て左から右へ移動してレーザー出力するようにしている。

結果としてはまぁまぁです。
始点や終点の【キレ】は、相変わらず甘いです。
キカイで調整できうるところは、別フェーズでやってみますが、
どこかに限界は有りそうな気はしてしまいます^^;


●SRC

  1. int laserCom = searchLaser();  
  2. printf(":%d\n",laserCom);  
  3. if(laserCom == 0){  
  4.     //    見つからない  
  5.     return    0;  
  6. }  
  7. laserOpen(laserCom); // 初期化はこの2行  
  8. laserInit();         // 初期化はこの2行  
  9.   
  10. setLineSpeed(500);    //      
  11. setForce(6);        //      
  12.   
  13. aLaserPic    AL;  
  14. AL.setStep(0.10);        //最少STEP(mm)  
  15. AL.setSize(30,0);        //実際の大きさ(mm)  
  16. AL.setOffset(10,10);    //左上座標  
  17. bool b = AL.loadPng("C:/temp/LS01.png");  
  18. int yline = AL.getPicHeight();  
  19. for(int y=0;y<yline;y++){  
  20.     double* dp = 0;  
  21.     int n = AL.getValue(y,&dp);  
  22.     if(!n) continue;  
  23.     double xx = dp[0] - 2.0;  
  24.     double yy = dp[1];  
  25.     moveTo(xx,yy);  
  26.     for(int w=0;w<n;w++){  
  27.         double x1 = dp[w*3+0];  
  28.         double y1 = dp[w*3+1];  
  29.         double x2 = dp[w*3+2];  
  30.         double y2 = dp[w*3+1];  
  31.         moveTo(x1,y1);  
  32.         lineTo(x2,y2);  
  33.     }  
  34. }  
  35. laserClose();  

●元データ



●元データ反転



●元データをスキャン(数値をファイル化してexcelで表示)




●竹材へ出力




●MDFへ出力

2016年6月21日火曜日

SmartLaser用のアプリを0から開発する。その5

いくつかテストを行い、まず、ソフトウエア基本部分を完成させてみます。
  1. int searchLaser(void);  
  2. bool laserOpen(int);  
  3. void laserInit();  
  4. int setForce(int);  
  5. int setLineSpeed(int);  
  6. int setMoveSpeed(int);  
  7. int moveTo(double,double);  
  8. int lineTo(double,double);  
  9. int laserClose(void);  
  10. //以下は直接は呼びません。  
  11. int writeLaser(const char*);  
  12. int readLaser(char*);  
  13. int readLaserEx(char*,int);  
とりあえず基本部分は、こんな形にしました。
実際には、C++でクラスのメソッドです。(だからHANDLEとかの引数が無い^^;)
もちろん、ラスタ系のコマンドを呼ぶ場合には、何かしら追加になりますね。

searchLaser
→COMポートを1~10までサーチし、レーザーが見つかったところで、COM番号を戻します。
見つからなければ0を返します。
実際には、SetCommStateして、"LasaurGrbl"という文字列を含むstramが戻ってくるかどうかで判断します。

laserOpen
→ COMをOPENします。
DCBとCOMMTIMEOUTSを設定するところまでやります。

laserInit
→なんでこうするかは別にして、
『既存のシステムがこんなん投げてるからとりあえず同じことをしておく』
ということをします。
とりあえずわかってることは、
"G90\x0aM80\x0aG0F8000\x0aG1F1500\x0aS8\x0a"
この部分だけ^^;^^;
あとは、関数の最後に原点をサーチして移動するようにします。

setForce
→レーザー強さの設定です。
ForceなのかPowerなのかStrengthなのかわかりませんが。。。^^;

setMoveSpeed
→動くSpeedを設定します

setLineSpeed
→レーザーしながら動くSpeedを設定します

laserClose
→まぁCOMをcloseするだけです。

2016年6月15日水曜日

SmartLaser用のアプリを0から開発する。その4

閉領域をちゃんと制御するためのテストです。
現状のSmartLaserCO2の出力ドライバ(Python)が、
いわゆる『閉領域』、 用語で言えば"FILL"に対応していません。 
ということで、実装するためにどのようなパラメータが必要かということを、
色々実験してみます。
    
これは拡大したもの。


いわゆる速度と強さは、Fxxxx、xxx%と言う形で指定するようになっているので、
これはわかるのですが、その他、
『FILL-LINEのピッチ』が重要です。
画像の材質は竹なのですが、0.2mmのピッチでも、間が残ることはありません。
でも、間が残るかどうかは、材質によって違いますし、
強さとか速度とかにも関係する部分でもあります。

ということは、FILLするには、、、
【移動速度】【レーザー強さ】【FILL-LINEピッチ】が基本パラメータとなります。
きっとこれは材質によってプリセットを登録しておくような使い方になるのでしょう。
しかし、SmartLaserCO2は、それだけではダメなようです。
一見、『速度が速く強さが強い』のと『速度が遅く強さが弱い』にとでは、
似たような結果になっても良いような気がしますが、
もちろん違うことはわかってはいたのですが、
決定的な差がありました。

それは、『エッジの切れ味』とでもいいますか、速度が速いとエッジがシャープになりません。
これは、レーザー照射とステッピングモータによる移動が、
完全に同期しているとは言えないかららしいです。
でも、今回はそっちまで踏み込むつもりは無いので、
速度を落とせばそこそこエッジもキレイになるので、それで調整してみることにします。

つづく

2016年6月11日土曜日

SmartLaser用のアプリを0から開発する。その3

SmartLaserCO2というのを研究開発目的で購入、
ソフトウェアの研究開発を開始です。
さて、codeを書いてみます。
ちょっと書きっぱなしな感じなのですが、テストとしてはこんな感じです。
起動して移動させてそれからリセット(HOMEへの移動)をさせてますが、
たぶん、このままではうまく動かないです。
タイミングをうまくとらないと、ダメですね。
ここらへんは、SleepやらRecieveのバイト数やら、いろいろやることになります。
実際にはデバッガでところどころ止めながらやってみたので、うまく動作したですね。

謎の文字列はどっから引っ張ってきたかというと、実際に元々のソフトウエァを動かして、
SerialPortMonitorなるものでキャップったからですね(o^^o)

  1. int aCom::test01(void){  
  2.  HANDLE cPort = ::CreateFileA("\\\\.\\COM3", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0);  
  3.  DCB dcb;     // シリアルポート構成情報STRUCT  
  4.  ::GetCommState(cPort, &dcb);// 現在の設定値をGET  
  5. /* 
  6.  dcb.BaudRate  = 57600;  // 速度 
  7.  dcb.ByteSize  = 8;   // データ長 
  8.  dcb.Parity   = NOPARITY;  // パリティ 
  9.  dcb.StopBits  = ONESTOPBIT; // ストップビット長 
  10.  dcb.fOutxCtsFlow = 0;   // 送信時CTSフロー 
  11.  dcb.fRtsControl  = RTS_CONTROL_ENABLE; // RTSフロー 
  12. */  
  13.  ::SetCommState(cPort, &dcb); // 取得したまま書き込む。こうしないと、データが戻らない?  
  14.   
  15.  unsigned long errors = 0;  
  16.  COMSTAT comStat;  
  17.  ::ClearCommError(cPort, &errors, &comStat);  
  18.  int lenR = comStat.cbInQue; // 受信したメッセージ長を取得する  
  19.   
  20.  for(int w=0;w<20;w++){  
  21.   if(lenR) break;  
  22.   ::Sleep(500);  
  23.   ::ClearCommError(cPort, &errors, &comStat);  
  24.   lenR = comStat.cbInQue;  
  25.  }  
  26.  if(!lenR){  
  27.   // Error  
  28.   printf("error-01\n");  
  29. //  return 0;  
  30.  }  
  31.  if(lenR > 100){  
  32.   // Error  
  33.   printf("error-02\n");  
  34. //  return 0;  
  35.  }  
  36.  char rBuf[10000];  
  37.  memset(rBuf,0,sizeof(rBuf));  
  38.  unsigned long nPut = 0;  
  39.  unsigned long nRead = 0;  
  40.  ::ReadFile(cPort, rBuf, lenR, &nPut, 0); // バッファからREAD  
  41.  // テスト環境では、【# LasaurGrbl 14.01\x0a】  
  42.  if(!strstr(rBuf,"LasaurGrbl")){  
  43.   // Error  
  44.   printf("error-03\n");  
  45. //  return 0;  
  46.  }else{  
  47.   printf("LasaurGrbl OK!\n");  
  48.  }  
  49.   
  50.  ::WriteFile(cPort, "\x14", 1, &nPut, 0); // ポートへPUT  
  51.  memset(rBuf,0,sizeof(rBuf));  
  52.   
  53.  ::ReadFile(cPort, rBuf, 10, &nRead, 0);  // バッファからREAD  
  54.   
  55.  // 謎の文字列送信  
  56.  ::WriteFile(cPort, "\x5e\x9f\x3f\x0a\x2a\x9f\x3f\x0a", 8, &nPut, 0);  
  57.  memset(rBuf,0,sizeof(rBuf));  
  58.   
  59.  ::ReadFile(cPort, rBuf, 100, &nRead, 0); // バッファからREAD  
  60.   
  61. //::Sleep(2000);  
  62.   
  63.   
  64.   
  65.   
  66.  ::WriteFile(cPort, "G90\x0aM80\x0aG0F8000\x0aG1F1500\x0aS77\x0a", 28, &nPut, 0);  
  67.  memset(rBuf,0,sizeof(rBuf));  
  68.  ::ReadFile(cPort, rBuf, 100, &nRead, 0); // バッファからREAD  
  69.   
  70.   
  71.   
  72.  // 移動させてみる。  
  73.  ::WriteFile(cPort, "G0X150.0Y150.0\x0a", 15, &nPut, 0);  
  74.  memset(rBuf,0,sizeof(rBuf));  
  75.  ::ReadFile(cPort, rBuf, 100, &nRead, 0); // バッファからREAD  
  76.   
  77.  // レーザーさせてみる。  
  78.  ::WriteFile(cPort, "G1X130.0Y130.0\x0a", 15, &nPut, 0);  
  79.  memset(rBuf,0,sizeof(rBuf));  
  80.  ::ReadFile(cPort, rBuf, 100, &nRead, 0); // バッファからREAD  
  81.   
  82.   
  83.  // 戻す  
  84.  ::WriteFile(cPort, "M81\x0aS0\x0aG0X0Y0F8000\x0a", 19, &nPut, 0);  
  85.  memset(rBuf,0,sizeof(rBuf));  
  86.  ::ReadFile(cPort, rBuf, 100, &nRead, 0); // バッファからREAD  
  87.   
  88.   
  89.   
  90.   
  91.   
  92. //::Sleep(2000);  
  93.   
  94.  // 謎の文字列送信原点の移動?  
  95.  ::WriteFile(cPort, "\x21\x0a", 2, &nPut, 0);  
  96.  ::WriteFile(cPort, "\x5e\x80\x7e\x0a\x2a\x80\x7e\x0a\x5e\x95\x47\x33\x30\x0a\x2a\x95\x47\x33\x30\x0a", 20, &nPut, 0);  
  97.   
  98.  memset(rBuf,0,sizeof(rBuf));  
  99.   
  100.  nRead = 0;  
  101.  while(nRead == 0)  
  102.  ::ReadFile(cPort, rBuf, 100, &nRead, 0); // バッファからREAD  
  103.   
  104.  printf("%s\n",rBuf);  
  105.  ::CloseHandle(cPort); // シリアルポートを閉じる  
  106.  return 0;  
  107. }  

2016年6月8日水曜日

SmartLaser用のアプリを0から開発する。その2

SmartLaserCO2というのを研究開発目的で購入、
ソフトウェアの研究開発を開始したのですが、、、、
いろいろ試してみて、現時点で品質を決定づける要因と、
ソフトウェアでカバーできうる可能性を考えます。

今回気づいた点をあげてみますと、
【1】 ラスタモードでの実用性はどうなのか?
【2】 VLS2.30との品質の比較で負けている点はカバーできるのか?
まず、出来上がりを見てみましょう。
以下の2つはVLS2.30というキカイで、MakersBaseというところでやってみたものです。
 
かなりキレイです。
但し、これ、全て閉領域の塗り(FILL)でデータをつくっています。
これを加工していて気づいた点は、全て、走査線のように、
キカイが動いていたという点です。

次にSmartLaserCO2での加工を見てみます。
まず、画像をラスタで加工してみました。
 
うぅーーん。かなり残念の結果です。
これ、このキカイにおける(というかArduinoにのってるソフトウェア?)ラスタデータの宿命なのかもしれません。
DOTを打っているかの如く、点々の集合のようになってしまいました。
もちろんすべてが画像データなので、走査線のように、キカイが動いていました。

次に、閉領域の如く、ハッチをつけたデータにしてみます。

ううぅぅぅーーん。これはまた究極的に残念な結果です。
走査線のように、キカイが動いているわけではありませんでした。
まぁもちろん、Arduino側でうまくソートがかかっているのかもしれませんが、
ここは時間を犠牲にしてでも、走査線のように動いて欲しかったですね。
とにかく美しくない仕上がりです。

さて、まぁまだ試作段階なので、これからがチューンです。
そもそもVLS2.30レベルの品質まであげることがソフトウェアだけでできるかどうかは、
現時点ではまだわかりません。
例えば、レーザー出力管の出力安定性が劣る場合には、それをソフトウェアでカバーすることは不可能です。

とりあえず方針をたて、さらにテストしながらトライ&エラー的に進めるしかないようです。

(1)データを、画像、閉領域、ベクタ図形の3種類にわけて読み込む。
(2)画像はBITフィールドのようなRAWデータと、走査線的なデータ変換の2種類のGコードの出力が可能なようにする。
(3)走査線の感覚は指定できるようにする。
(4)線幅に対応する。これはズラしてベクタデータを2重3重4重、、、、、に描かれているかのごとくヘッドが動くように考える。


現段階では、【カットには使えるけれど、彫刻的にはこのままの品質だと残念過ぎる 】
とい感じですね。


つづく

2016年6月3日金曜日

SmartLaser用のアプリを0から開発する。その1

SmartLaserCO2というのを研究開発目的で購入したのですが、

出力用のアプリがかなり残念な出来なので、
これを0からというか1から開発してみます。
まぁもちろん、前情報としてかなり問題があること自体はわかっていて
手持ちのイラレで作ったSVGがかなりの確率で読み込めませんし、
閉領域もNGだし、画像もかなりアバウトです。

※Pythonコードに手を入れるというメンドイことはやりません。^^;



最初の段階でやってみるのは、解析とテストです。
幸い、現状のアプリにはGコード出力機能があるので、それを多少の手がかりにして進めます。


今回は、SmartlaserCo2(mini)は、USB接続によるCOMポート出力なので、
COMポート一覧を取得してSmartLaserが接続されているCOMポートかどうかを判断する部分から始めます。
ということで、C++(Windows)で書くと、こんな感じ。
JSONを扱うクラスが数か所でてきますが、これは値を保存する構造体代わりに使っています。
取得したサンプルはこんな感じ。
  1. /* 
  2.     COMポートリスト取得 
  3.     ↓結果サンプル 
  4. { 
  5.     "comlist":[ 
  6.         { 
  7.             "friendlyname":"通信ポート (COM1)" 
  8.             ,"name":"COM1" 
  9.         } 
  10.     ] 
  11. } 
  12. */  
コードはこんな感じです

  1. int aCom::getComList(aDom8* pZ){  
  2.     aDom8& Z = *pZ;    //    JSON-Object  
  3.     char    buffer[256];  
  4.     memset(buffer,0,sizeof(buffer));  
  5.     DWORD    Length = 0;  
  6.     HDEVINFO hDevInfo = ::SetupDiGetClassDevs( &GUID_DEVINTERFACE_COMPORT, NULL, 0/*hwnd*/, ( DIGCF_PRESENT | DIGCF_DEVICEINTERFACE ) );  
  7.     DWORD w=0;  
  8.     SP_DEVINFO_DATA DeviceInfoData = {sizeof(SP_DEVINFO_DATA)};  
  9.     for(w=0; ::SetupDiEnumDeviceInfo( hDevInfo, w, &DeviceInfoData ); w++){  
  10.         ::SetupDiGetDeviceRegistryPropertyA( hDevInfo, &DeviceInfoData, SPDRP_FRIENDLYNAME, 0, (unsigned char*)buffer,sizeof(buffer),&Length );  
  11.         Z[w]["friendlyname"] = (const char*)buffer;    //    JSON-Object  
  12.         HKEY key = ::SetupDiOpenDevRegKey( hDevInfo, &DeviceInfoData,  DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE );  
  13.         if(key){  
  14.             DWORD type = 0;  
  15.             DWORD size = sizeof( buffer );  
  16.             ::RegQueryValueExA(key,  "PortName", 0, &type, (unsigned char*)buffer, &size);  
  17.             Z[w]["name"] = (const char*)buffer;    //    JSON-Object  
  18.         }  
  19.     }  
  20.     return    w;  
  21. }  
っと、ここまではよかったのです。
きっと、【Arduino Mega 2560(COM3)】みたいな感じで、friendlynameが取れると思ってたのです。
しかし、、、残念ながらこのやりかたでは、仮想COMが取れませんでした。
んーーー。
仕方ない。。。WDKいれて、やるか。。。(>_<)

つづく。

2016年6月2日木曜日

Smart Laser CO2 というのを組み立てる。の3※Inkscapeは私の知っているDPIでは無かった件。


SmartLaserCO2というのを研究開発目的で購入。の3

時間がだいぶかかりましたが、なんとか完成しました。

まぁ水冷の冷却装置が、個別のパーツもすぐ壊れそうだし、
わざわざアクリルでつくってるのもよくわかんないし、
さらにネジの締め方がありえない弱い構造で、
あまりにも壊れそうなのにもかかわらず、
値段的に甚だ不満があるのはさておき、
とりあえず、S/Wをインストールしてサンプルをダンボールでテスト。


予想を上回るなかなか良いできです。
そして、精度をはかるために、100□80□60□40□20□で板に刻印。

よさげなんだけど、、、寸法を計ってみると、100mmのところが99.6mm。
でも、XもYも同じくこの値なので、機械的には問題無いと予測。

なんかFBのグループでちょこちょこ報告されてるような、
ズレちゃうとか、、直角がでないとか、そういうのはありません。
機械的に問題のありそうなところはかなり気合をいれて作ったので、
それがよかったのでしょう。

で、調査開始です。
このS/W、『Gコードで保存』ができるので、それを保存し、
TABを自分で入れて、EXCELに貼り付け。
まずは座標のグラフ化。


なるほど。なぜこれだけ制御点(正確な呼び名は知りませんが)が多いのかは謎ですが、
それは後回しにして、X,Yそれぞれの座標のMIN,MAXを調べると、
その差は、99.59。

そう、できあがりの99.6は全然間違ってませんでした。

ではどこが問題なのかと言うと、、
『Inkscapeは単位がpixで90DPI』という情報がありまして、
実際、読み込むときも90DPIを指定して読み込んでいます。

いやー、しかし、DPIってのは本来、ラスタ系の解像度を示す単位なので、
ベクタ系のデータとして使われてるのはちょっと違和感がありますが、、
それはおいといて、電卓をあれこれ叩いた結果、
【Inkscapeの90DPIのIは、25.4ではなく25.5である】
という結論に達しました。
この分だけ、誤差というか、、誤った値として取り込まれてしまっています。

この情報、間違いでした。申し訳ありません。
どうも、Inkscapeのデータの持ち方が、イラレ/PDF等のフォーマットとちょっと違う持ち方のようなので、もう少し解明してからまた記述します。

それにしても、まぁ研究開発のテーマがS/Wだから良いのですが、
この出力のためのS/W、使い勝手も、機能も、その完成度も、
今どきとしては考えられないような品質。
やりがいがとてもありそうです(o^^o)







2016年6月1日水曜日