管理員
統計數據
原文網址 石小川 2019-12-05 07:42:33
此種工業相機可用程式控制內部一些參數,對於機器視覺有興趣的朋友可以參考一下此套件。 技術提示 : 值得一提的是此款工業相機是可以自已寫程式控制參數的, 例如圖像尺寸(ROI)、拍照、曝光時間、GAMMA、對比差、亮度、自定義LUT、鏡頭翻轉、RGB顏色增益、飽和度、銳度、彩色轉黑白、色溫校正、反色、9組十字線位置和顏色、觸發模式、幀率控制,有附SDK及C#、C++ Builder、Delphi、labView、QT5、VB、VC等範例可參考,對於喜歡寫自動控制程式的人來說非常方便。 鏡頭及相機可拆賣, 有(A)、(B)、(C)三個套餐可選,下標後請註明套餐並自行修改金額即可, 謝謝惠顧! (A). 工業相機usb高清300萬像素 支持Halcon 工業摄像頭機器視覺相機送SDK + 高清2.8-12mm300萬像素手動光圈鏡頭無畸變可 調視覺工業檢测镜頭 - NT$4100 (B). USB高清300萬像素工業相機機器視覺相機送SDK – NT$2500 (C). 高清2.8-12mm300萬像素手動光圈鏡頭無畸變可調視覺工業檢测镜頭 – NT$1600 https://goods.ruten.com.tw/item/show?21925793582980
Wilson Kao 2020-02-26 16:05:59
這個可以用在紡織業瑕疵檢測?
原文網址 石小川 2019-12-04 11:48:08
電源供應器常見交流電輸入端子解釋 : L : 火線(白色)。 N : 中性線(黑色)。 E : 地線(綠色)。 常用接點名詞解釋 : 乾接點 : 無源開關,沒有極性,如繼電器、按鍵開關。 溼接點 : 有源開關,有極性,如電晶體、MOSFET開關。 NO : 開路接點(又稱a接點)。 NC : 閉路接點(又稱b接點)。 常用電線規格 : @變頻器220V 電源線 : 600V - 5.5m2 3C (3芯) @步進驅動器信號線 : 0.65mm2 x 2P (電話線4芯) 0.5mm2 x 2P @57步進馬達激磁線 : 18AWG x 2C [Example]舉一個主軸馬達變頻器,輸入AC單相220V/3KW為例子,可以很簡單的估算出用哪種線材。 A = 3000/220 = 13.6安培,大約耗電量14安培 一般我們抓1.5倍安全係數, 安全電量 = 14A x 1.5 = 21A 所以我們取 安全線徑 = 3.5mm ~ 5.5mm絞線 p.s. 線材用法可參考圖片,信號線很適合用交換機的多芯線、網路線、或排線防干擾。 表 1-1. 硬質PVC 3C,35度C線徑安全電流係數對照表 單線 1.6mm -> 15A 單線 2.0mm -> 19A 單線 2.6mm -> 26A 絞線 3.5mm -> 20A 絞線 5.5mm -> 25A 絞線 8.0mm -> 33A 絞線 14mm -> 50A 絞線 22mm -> 60A 絞線 30mm -> 75A 絞線 38mm -> 85A 絞線 50mm -> 100A 絞線 60mm -> 110A 絞線 80mm -> 140A 絞線 100mm -> 160A 絞線 125mm -> 185A 絞線 150mm -> 215A 絞線 200mm -> 225A 絞線 250mm -> 300A 絞線 325mm -> 355A 絞線 400mm -> 355A 絞線 500mm -> 460A
原文網址 石小川 2019-12-02 09:58:12
參考圖運動卡應用圖例,在剖析程式之前有必要先簡單介紹一下iMC3082E 運動卡的主要功能,這樣子讀者在閱讀整個程式流程時會較為清楚,在第六章硬體篇還會再詳談CNC機構、軸控、電控等相關知識以及組裝、接線、試俥相關技術。
iMC3082E 運動卡主要功能如下 :
(1)運動卡與PC主控台通信是採用100MHz乙太網路。
(2)軸控制 : 8軸。
(3)輸出控制接點 : 48個。
(4)輸入控制接點 : 80個。
而iMC408A是增強版,功能多了下列三項 :
(5)類比轉數位(AD) : 8路。
(6)數位轉類比(DA) : 4路。
(7)PWM : 4路
如果軟體封裝的夠好,一般寫控制軟體只要知道上述硬體規格,細節就由底層API負責即可,讀完本書後應有能力建構這種等級的軟體工程。
圖1. 八軸運動+I/O+AD/DA控制卡應用圖例
原文網址 石小川 2019-11-30 20:38:34
接下來預計要推智慧型機台硬體篇以及如何結合人工智慧的應用軟體開發, 希望軟體軸控班畢業的同學們能短時間自組一台三軸機台, 軟硬兼施很快就可累積經驗。 現在我將上課機台零組件規格列出如下給同學參考, 請回家自組機台(常見的零件就不附上網址了), 電源線材方面的規格安培數估算及信號線此次沒來得及po, 有組裝任何問題可mail我, 一有空會盡快回覆問題。 --------------BOM-------------- @機構 - 名稱、規格 A1. 鋁擠: JA4040W _4系列鋁擠型 https://goods.ruten.com.tw/item/show?21707258866136#info A2. 連結器 + 螺絲 + 螺帽 組: JL4040-組(含不鏽鋼螺絲墊片、鍍鎳螺帽 ) https://goods.ruten.com.tw/item/show?21707258866136#info A3. 滾珠螺桿+螺母 螺桿: SFU1605 https://goods.ruten.com.tw/item/show?21710445745909 註規格:直徑16mm, 導程5mm, 長度700mm. 兩端需加工成光軸直徑10mm, 一端長度40mm, 另一端長度20mm. (可訂製 SFU系列滾珠螺桿 螺母 SFU1204 1605 1610 2005 2010 2505等) A4. 滾珠螺桿固定座 EK10 https://goods.ruten.com.tw/item/show?21718087012287 A5. 滾珠螺桿螺母固定座 SFU1605 https://goods.ruten.com.tw/item/show?21710445617288 A6. 光軸: D20mm x L700mm https://goods.ruten.com.tw/item/show?21435061373353 A7. 光軸固定器: SK20 https://goods.ruten.com.tw/item/show?21521489690535 A8. 線性軸承: SC20LUU 加長連座直線軸承 https://goods.ruten.com.tw/item/show?21613315109888 A9. 主軸夾: 80mm /100mm A10. 聯軸器: 梅花聯軸器8mmx10mm D30 L42 @電控 - 名稱、規格 B1. 運動卡: iMC404A 運動卡可連絡我訂購, 推薦用此款, 有4軸、AD/DA及PWM, 方便將來擴充功能 B2. 驅動器: DM542 B3. 57步進馬達: 57BYGH78 B4. 16路繼電器 https://goods.ruten.com.tw/item/show?21309058946108 B5. 電源供應器: 24V/20A https://goods.ruten.com.tw/item/show?21647504008827 B6. 水冷式主軸 : 振宇雕刻机主轴电机(80/100mm)3.2KW金属模具 B7. 變頻器: 主轴BEST变频器1.5KW-7.5KW贝士德变频器调速器 B8. 機殼: LEOPARD 4U工業機殼 (加長版) LE-E4061 F23 https://goods.ruten.com.tw/item/show?21639787647077 https://goods.ruten.com.tw/item/show?21637592560499 B9. 警示燈: 50mm 警示燈 三層 常亮含蜂嗚器 LED燈泡 https://goods.ruten.com.tw/item/show?21819996601798 B10. 坦克鏈: 15x40半封閉內開R48尼龍拖鏈 https://goods.ruten.com.tw/item/show?21805654084878 p.s. 零組件自行選配
原文網址 石小川 2019-11-29 09:11:49
p.s. 雷達是機器的眼睛,以後還會多方介紹這類的遙測技術,掌握這門知識對於設計智慧型機器幫助會很大 。 光學雷達(LiDAR)這幾年很紅, 從汽車自動駕駛、火炮射控、巡弋飛彈,、機器人或掃地機等都可看到蹤影, 因為有人問我光學雷達的點雲(point cloud)資料如何產生, 所以今天簡單講解一下點雲基礎。 其實原理很簡單, 由雷達發射的仰角α(elevation), 水平角β(azimuth)及接收訊號的距離r(distance), 參考下面圖的座標三角函數關係就可得知目標物的方位等資訊P(x’, y’, z’), 一連串的數據產生這就是點雲的故事了。其格式如下: Point cloud = {P1, P2, P3…..Pn}; //表示一組點雲數據 Pi = [xi’, yi’, zi’]; //每一個點雲就是一個座標 更進階的點雲還加入目標物的RGB顏色, 灰階值等資訊φi, 則格式會變成 Pi = [xi’, yi’, zi’, φi]; 下面是點雲公式推導, 參考一下就好. 定義符號, 仰角 : α(elevation) 水平角 : β(azimuth) 距離 : r(distance) r’ = r * cosα (1) x’ = r’ * cosβ (2) y’ = r’ * sinβ (3) z’ = r * sinα (4) 將 (1)式代入(2), (3)式, 重新整理如下: x’ = r * cosα * cosβ (5) y’ = r * cosα * sinβ (6) z’ = r * sinα (7) 從LiDAR發射光點及接收訊號, 可以知道(α,β,r)參數, 所以很快就可求出目標物的方位值(x’, y’, z’)。 p.s. 下面程式(LiDAR + 3軸雲台控制)是我以前用Arduino寫來測試LiDAR用的, 有點長慎入, 日子久了有點不知程式在寫啥! 程式沒有最佳化但可以動作, 純粹Demo點雲公式如何使用給有興趣的好友參考一下, 有興趣的朋友也可改成Python版本嘉惠其他人, 如果有不解地也可私我。 //Program of LiDAR******************************** #include <string.h> #include <Servo.h> #include <LIDARLite.h> //----------------------------------------------------------------------------- #define Def_minAzimuth 30 #define Def_maxAzimuth 160 #define Def_standAzimuth 90 #define Def_minElevation 0 #define Def_maxElevation 60 #define Def_standElevation 0 #define Def_offsetElevation 90 //----------------------------------------------------------------------------- LIDARLite _lidar; Servo _servoX; Servo _servoY; // Minimum and maximum servo angle in degrees // Modify to avoid hitting limits imposed by pan/tilt bracket geometry int _minPosX = 30; //0; int _maxPosX = 160; // 180; //Servo real position int _minPosY = 90; int _maxPosY = 100; int _midPosX = 90; //(_maxPosX + _minPosX) / 2; int _midPosY = 100; //(_maxPosY + _minPosY) / 2; boolean _scanning = false; boolean _scanDirectionX = false; boolean _scanDirectionY = false; int _posX = 0; int _posY = 0; //pole coordinate int _Azimuth = 0; int _Elevation = 0; int _Distance = 0; unsigned int _loopCount = 0; //----------------------------------------------------------------------------- void setup() { _lidar.begin(0, true); _lidar.configure(0); _servoX.attach(2); _servoY.attach(3); _Azimuth = Def_standAzimuth; //90 //_Elevation = Def_standElevation + Def_offsetElevation; //0 - 90 _Elevation = Def_standElevation; //0 ServoHome(_Azimuth, _Elevation); LidarON(); Serial.begin(115200); delay(100); } //end setup() //----------------------------------------------------------------------------- void loop() { int px, py; int length, count; char command[12], code[12]; String data[6]; delay(1000); if(length = Serial.available()) { Serial.readBytes(command,length); command[length]= '\0'; count = CommandParse(command, data, " "); DataParse(data[0], code); if(code[0] == 'C') { switch(code[1]) { case '0': //Serial.println("C0 OK!"); break; case '1': ServoHome(_Azimuth, _Elevation); Serial.println("C1 " + String(_Azimuth) + " " + String(_Elevation)); delay(1000); break; case '2': //指向測距 DataParse(data[1], code); _Azimuth = atoi(code); DataParse(data[2], code); _Elevation = atoi(code); LidarScan(_Azimuth, _Elevation, true); //px= _Azimuth; //py = _Elevation + 90; //ServoMove(px, py); //delay(100); Serial.println("C2 " + String(_Azimuth) + " " + String(_Elevation)); break; case '3': Serial.println("C3"); break; case '4': //全面掃描 LidarScan(_Azimuth, _Elevation, false); ServoHome(_Azimuth, _Elevation); //Serial.println("C4"); break; } //end switch(command[1]) } //end if(command[0] == 'C') else { /* while(Serial.read() != -1) { delay(100); } */ //Serial.println("Error"); } } //end if(length = Serial.available()) } //end loop() //----------------------------------------------------------------------------- int CommandParse(char *command, String data, char delimit) { int index; char *ptr; index = 0; ptr = strtok(command, delimit); data[index] = (String)ptr; index++; while ((ptr = strtok(NULL, delimit))) { data[index] = (String)ptr; index++; } //end while ((ptr = strtok(NULL, delimit))) return index; } //----------------------------------------------------------------------------- void DataParse(String data, char *code) { int i; for(i=0; i < data.length(); i++) code[i] = data.c_str()[i]; code[i] = '\0'; } //----------------------------------------------------------------------------- //azimuth : 0 - 180. elevation : 0 - 90 void ServoHome(int azimuth, int elevation) { int i; //Handle Azimuth if(azimuth >= Def_standAzimuth) // >= 90 { for(i = azimuth; i > Def_standAzimuth; i--) { _servoX.write(i); delay(10); } } else if(azimuth < Def_standAzimuth) // < 90 { for(i = azimuth; i < Def_standAzimuth; i++) { _servoX.write(i); delay(10); } } _Azimuth = i; //Handle Elevation if(elevation >= Def_standElevation) // <= 0 { for(i = elevation; i > Def_standElevation; i--) { _servoY.write(i + Def_offsetElevation); delay(10); } } else if(elevation < Def_standElevation) { for(i = elevation; i < Def_standElevation; i++) { _servoY.write(i + Def_offsetElevation); delay(10); } } _Elevation = i; } //end void ServoHome() //----------------------------------------------------------------------------- //azimuth, elevation, is currently position void LidarScan(int azimuth, int elevation, bool enable) { int i, j; unsigned int loopcount, distance; bool bDirection; if(enable) //單點掃描 { ServoMove(azimuth, elevation + Def_offsetElevation); delay(1000); //少了distance會錯誤 distance = _lidar.distance(false); Serial.println(String(azimuth) + " " + String(elevation) + " " + String(distance)); i = azimuth; j = elevation; } else //全區掃描 { //initial servo start point x=0, y=0 for(i = azimuth; i > Def_minAzimuth; i--) { _servoX.write(i); delay(10); } //initial servo start point y=0 for(i = elevation; i > Def_minElevation; i--) { _servoY.write(i + Def_offsetElevation); delay(10); } loopcount = 0; bDirection = true; for(j = Def_minElevation; j < Def_maxElevation; j++) // Scan Y { if(bDirection) { bDirection = false; for(i = Def_minAzimuth; i < Def_maxAzimuth; i++) { loopcount++; if (loopcount % 100 == 0) distance = _lidar.distance(); else distance = _lidar.distance(false); Serial.println(String(i) + " " + String(j) + " " + String(distance)); ServoMove(i, j + Def_offsetElevation); } //end for(i = Def_minAzimuth; i < Def_maxAzimuth; i++) } else { bDirection = true; for(i = Def_maxAzimuth; i > Def_minAzimuth; i--) { loopcount++; if (loopcount % 100 == 0) distance = _lidar.distance(); else distance = _lidar.distance(false); Serial.println(String(i) + " " + String(j) + " " + String(distance)); ServoMove(i, j + Def_offsetElevation); } //end for(i = Def_maxAzimuth; i > Def_minAzimuth; i--) } /* for(i = Def_minAzimuth; i < Def_maxAzimuth; i++) { loopcount++; if (loopcount % 100 == 0) distance = _lidar.distance(); else distance = _lidar.distance(false); Serial.println(String(i) + " " + String(j) + " " + String(distance)); ServoMove(i, j + Def_offsetElevation); } //end for(i = Def_minAzimuth; i < Def_maxAzimuth; i++) delay(10); for(i = Def_maxAzimuth; i > Def_minAzimuth; i--) { loopcount++; if (loopcount % 100 == 0) distance = _lidar.distance(); else distance = _lidar.distance(false); Serial.println(String(i) + " " + String(j) + " " + String(distance)); ServoMove(i, j + Def_offsetElevation); } //end for(i = Def_maxAzimuth; i > Def_minAzimuth; i--) delay(10); */ } _Azimuth = i; _Elevation = j; } } //----------------------------------------------------------------------------- void ServoX(int px) { _servoX.write(px); delay(10); } //end void ServoMove() //----------------------------------------------------------------------------- void ServoY(int py) { _servoY.write(py); delay(10); } //end void ServoMove() //----------------------------------------------------------------------------- void ServoMove(int px, int py) { _servoX.write(px); _servoY.write(py); delay(10); } //end void ServoMove() //----------------------------------------------------------------------------- void LidarON() { _scanning = true; _loopCount =0; } //end void LidarON() //----------------------------------------------------------------------------- void LidarOFF() { _scanning = false; _loopCount =0; } //end void LidarOFF() //----------------------------------------------------------------------------- /* bool LidarScan() { if(_scanning) { _loopCount ++; if (_loopCount % 100 == 0) _Distance = _lidar.distance(); else _Distance = _lidar.distance(false); _Azimuth = _posX; _Elevation = _posY - 90; Serial.println(String(_Azimuth) + " " + String(_Elevation) + " " + String(_Distance)); ServoMove(_posX, _posY); if(_scanDirectionX) _posX++; else _posX--; if(_posX >= _maxPosX || _posX <= _minPosX) { _scanDirectionX = !_scanDirectionX; if(_scanDirectionY) _posY--; else _posY++; if(_posY > _maxPosY || _posY < _minPosY) { _scanDirectionY = !_scanDirectionY; LidarOFF(); //ServoHome(); return true; } } } //end if(_scanning) return false; } //end void Lidar()
原文網址 石小川 2019-11-27 15:30:33
現今開發應用程式已經很少不用視窗當作人機介面(HMI)了,同樣的,如果沒有用圖控方式操作自動化設備的話,相信機器應該也很難賣得出去,就是因為圖控是很重要的一環,所以我特別在這一章節簡潔的介紹一個開發圖控介面不錯的工具 – PyQt5給讀者參考,慎選好的開發工具的確可以讓你省下不少寶貴的時間。 這裡會用一個實例讓讀者了解Python結合圖形視窗的設計流程,熟悉之後在設計儀表控制、機器自動控制等介面將不在是難事, 有興趣的讀者還可改成流行的拖放( drag and drop )圖控應用程式,投資自已學習這門工具是值得的,下面就一步步來實現。 3. 1 安裝 PyQt5 前面如果沒有安裝,現在趕快安裝,方法如下: 在Anaconda Prompt 視窗(Windows 命令提示字元)下先後輸入 pip install PyQt5 pip install PyQt5-tools 安裝完成後最好重開機,在路徑資料夾Anaconda3\Library\bin內會有個執行 檔案designer.exe,請拉到桌面方便使用(注意你的路徑及資料夾可能跟我的是不一樣),至此安裝結束, 接下來可以打開designer設計圖控介面了,如圖3-1所示,我們會用它來設計一個簡單的圖控介面,並展示如何用Python程式引用這個介面的相關知識。 3. 2 認識QT Designer 認識一下圖3-1 QT Designer視窗中4個紅字標註區域。 區域1是元件盒,裡面提供很多的元件控制項,每個項目的功能不同 ,例如常用到的按鈕(Push Button)、單選紐(Radio Button)、及 標籤(Label) 、文字方塊(TextEdit)等,可以直接拖放到區域2主視窗中擺放,在區域3物件 指示器(Object Inspector)中可索引切換擺放在主視窗中不同的元件,利用屬性編輯器(Property Editor)對選中的元件編輯如寬度、高度、佈局、字型等屬性。 區域4是信號/信號槽編輯器,這是PyQt5非常重要的核心機制,當按紐按下後就會發射一個信號(signal),信號槽(slot)接收到這個對應的信號後就會處理這個事件。 Qt Designer 這一個視覺化的GUI設計工具,對程式設計師來說真的是一大福音,便利了許多, 有一點要提想讀者的是PyQt5在GPL協議是可以完全免費使用,但如果不打算開源準備應用於商業活動還是得付費。當然啦,熟悉了Designer UI設計後,一大票相似免費軟體等著你,不怕被綁架! 如果讀者想更深入了解Qt5及程式設計,可至Qt官網下載資料好好研究。 Qt 官網 : https://www.qt.io/download-qt-installer 3. 3 使用QT Designer 設計一個UI介面 步驟 1. 點擊下拉選單[檔案]新增一個新表單(圖3-2)。 圖 3-2 新建一個新表單。 步驟2. 選Widget 表單(圖3-3)。 圖 3-3 Widget表單。 步驟3. 拖放元件到表單(圖3-4)。 從元件盒拖放一個Label 及Push Button 到表單(Form)上(圖 3-4)。 可在物件指示器及屬性編輯器上修改元件名稱、屬性。 在此為了簡化問題,我就維持不變,讀者熟悉後可自已動手試試。 圖 3-4 設計表單。 步驟4. 編輯信號與信號槽。 要讓按鈕按下有作用,還需要將信號和槽關聯起來, 如圖3-5點擊工具列[編輯信號與信號槽] 圖 3-5 編輯信號與信號槽 步驟5. 關聯信號與信號槽。 滑鼠游標移到表單(Form)的元件按鈕(PushButton)處,左鍵按著不放移動到 表單可看到一個接地線,之後放開左鍵會彈跳出一個設定連線的視窗(圖3-6)。 圖3-6 關聯信號與信號槽 步驟6. 編輯信號槽事件函數。 選定 pushButton 發射信號是clicked(),之後點擊右邊Form下的編輯按鈕 (圖3-7)和(圖3-8)。 圖3-7 pushButton -> clicked() 步驟7. 新增信號槽事件函數。 按+新增一個自訂pushButton_click()事件函數()(圖3-8), 按OK鍵離開。 圖3-8 編輯信號槽 步驟8. UI設計完成。 自動跳回主視窗後可看到自建的UI成形了,不要忘記存檔,例如檔名myUI.ui (副檔名ui),要增加或修正UI也可開啟舊檔編輯,不需要從無到有再搞一 次,是不是挺方便的。 圖3-9 UI 步驟9. ui轉檔py。 Qt Designer 儲存的是.ui檔,結構類似XML格式,因為我們期望是要給Python引用,所以我們必須想辦法將.ui檔轉換成.py檔才行。 PyQt5提供一個命令列工具pyuic5可以很輕鬆地完成轉換工作,使用方式如下 : pyuic5 source.ui -o destination.py 其中 -o : 是輸出参数,表示要產生一個文件 destination.py : 要產生.py格式的文件 source.ui : 由Qt Designer設計UI的.ui原始文件 舉個例子, 假設你設計出來的UI檔名是 myUI.ui,想轉檔成myUI.py格式 可以照著圖3-10這樣做,在相同路徑下會自動產生myUI.py檔。 如果你常用pyuic5,且對Python語法熟悉的話,建議可自已寫個簡單腳本,將上述命令列改成自動化執行,這個就留給讀者自已練習了。 圖3-10 透過pyuic5將.ui 轉換成.py 上述步驟雖多其實並不複雜,讀者還是要實際操作一次才會真正了解其中的奧義,UI美不美觀端看個人造詣,但設計的方法都是一樣的,多練習了。 緊接著下個單元會舉一個實例說明Python引用myUI.py的方法。 3. 4 Python 引用UI介面 新建一個程式碼myApp.py並繼承介面檔(myUI)的Ui_Form類別即可(打開 myUI.py 檔就可看到類別class Ui_Form)。 其中myUI.py介面檔必須放在myApp相同資料夾內,否則會錯誤。 程式碼執行如圖3-11,按一下PushButton會show出 "Hi this is my first coding !!!",結果正如預期完全正確。myApp.py可當作一個樣板程式,讀者可慢慢擴增一些新功能,建議每加入一個功能最好就執行一次,沒問題了再增加,以免欲速則不達,將來花更多的時間debug,會抓到瘋,畢竟最複雜的程式也是從最基本的一磚一瓦造出來的,除非讀者寫程式功力深厚以及擁有的程式庫經過多年考驗沒問題,否則還是聽我的準沒錯。 myApp.py程式碼如下: - - coding: utf-8 - - import sys from PyQt5.QtWidgets import QMainWindow, Qapplication from myUI import Ui_Form #------------------------------------------------------------------------------ class MyWindow(QMainWindow, Ui_Form): def init (self, parent=None): super(MyWindow, self). init (parent) self.setupUi(self) #------------------------------------------------------------------------- def pushButton_click(self): self.label.setText("Hi this is my first coding !!!") #------------------------------------------------------------------------------ def main(): app = QApplication(sys.argv) myWin = MyWindow() myWin.show() sys.exit(app.exec_()) if name == ' main ': main() 圖3-11 myApp 程式碼及執行結果
陳必凱 2019-11-27 15:40:03
曾彥禮 2019-11-27 16:24:58
哇
曾希哲 2019-11-27 19:40:06
有designer 寫起來跟Visual basic 一樣方便
林志強 2019-11-27 22:08:29
周英男 2019-11-27 23:22:18
好想學
原文網址 石小川 2019-11-27 11:09:22
窮忙一陣子後今天終於可偷閒寫一下文章跟大家分享一下上回談的G-Code產生器了. 我用最簡單的CNC圓形切割來圖文解釋一下, 相信應該可以很快了解其中運作原理.
先解釋一下Entity名詞, Entity在CAD工程上是實體的意思, AutoCAD出圖後的元件我們稱為Entity, 如圓弧, 直線, 雲行線等皆是實體. 上回提過由DWG/DXF檔案 經過程式剖析分解後產生一連串Entity的資料, 如圖一, 是圓的Entity, 將它送入GCodeCircle(Entity) 函式就可很快地產生切圓的G-Code, 下面是GCodeCircle(Entity) C#版程式範例:
//------------------------------------------------------------------------- public string GCodeCircle(EntityRecord entityrecord) { string gCode = ""; Point3D center = new Point3D(entityrecord.Circle.Center.X, entityrecord.Circle.Center.Y, entityrecord.Circle.Center.Z); double radius = entityrecord.Circle.Radius; Point3D point = new Point3D(0, 0, 0); point.X = center.X + radius; point.Y = center.Y; gCode += "G00 X" + (point.X - center.X).ToString("#0.0000") + " " + "Y"+ (point.Y - center.Y).ToString("#0.0000") + " " + "Z5.0000"+ "\n"; //此段是為了說明已經簡化許多參數 gCode += "G01" + " " + "Z-1.0000"+ " " + "F200"+ "\n"; gCode += "G02" + " " + "I-"+ radius.ToString("#0.0000"); return gCode; }
圖二是產生的G-Code, 可直接送去機器做切割一個100mm的正圓.
另外之前有位朋友問我5軸聯動切向跟隨實作問題, 我列出部分程式碼給您參考一下, 基本上我大部分實作都是圓弧直線插補ArcLine()就可完成.
//--------------------------------------------------------------------------- //圓弧直線跟隨插補 //pos : Z軸直線運動位置 //axisNum = 3, 圓弧+直線運動軸數 X Y Z public boolArcLine(int startx, intstarty, int endx, intendy, int cx, intcy, int[] pos, int[] axis, int dir = 0, intaxisNum = 3, double acc = 10, doubletgvel = 100, double endvel = 0, doublefeedRate = 1.0, int wait = 1, intfifo = (int)FIFO_SEL.SEL_PFIFO1, boolbAbs = true) { int st; if(axisNum < 2 || axisNum > MAX_NAXIS) return false; //清空PFIFO st = IMC_Pkg.PKG_IMC_PFIFOclear(gHandle, fifo); if(st == 0) return false; //設置加速度和進給率 if(SetPFIFO(acc, feedRate, fifo) == false) return false; //映射軸 st = IMC_Pkg.PKG_IMC_AxisMap(gHandle, axis, axisNum, fifo); if(st == 0) return false; double dx,dy; dx = startx - cx; dy = starty - cy; double r1 = Math.Sqrt(dx * dx + dy * dy); dx = endx - cx; dy = endy - cy; double r2 = Math.Sqrt(dx * dx + dy * dy); //判斷, 如果起點到圓心距離r1 != 終點到圓心距離r2 表示圓弧軌跡不正確, 跳出不執行 if(r1 != r2) return false; //由當前位置移動到指定位置 if(bAbs) //絕對位置 st = IMC_Pkg.PKG_IMC_ArcLine_Pos(gHandle, endx, endy, cx, cy, dir, pos, axisNum - 2, tgvel, endvel, wait, fifo); //絕對位置 else st = IMC_Pkg.PKG_IMC_ArcLine_Dist(gHandle, endx, endy, cx, cy, dir, pos, axisNum - 2, tgvel, endvel, wait, fifo); //相對位置 if(st == 0) return false; return true; }
圖三是我執行雷射切割的機台, 雷射頭是5.5W藍光雷射, 下回再分享此G-Code雷射切割成果.
p.s. 在我的APP程式直接畫圖我是直接呼叫運動卡驅動CNC, 沒有經過G-Code產生器這道手續, 加快CNC處理速度, 除非是匯入DWG/DXF檔案才會呼叫G-Code 產生器。
鍾小生 2019-11-27 12:24:32
小聲問,那套App有試用版嗎?
原文網址 石小川 2019-11-27 10:57:00
對於想用CNC製作一些零組件的朋友來說,要學會AutoCAD、SolidWork、uG、Fusion360、ARTCAM、PowerMill、 MasterCAM、Mach3等,我相信會嚇跑一票人的熱血。其實CAD/CAM套裝軟體很強但有很多功能幾百年也用不到,只是浪費錢而已,在接工廠自動控制客製化軟體方面絕大部分作動都很單純,我這裡是將所有功能精簡成 : /檔案/機器/刀具/G-Code sender,只要讀檔或在螢幕寫字、繪圖後自動產生G-Code直接送至CNC銑床或雷射加工,盡量能由機器代勞的就盡量隱藏在程式裏面,操作介面就只有一個螢幕面板Panel。
要做到這點須先解析DWG檔案結構,後續追加功能才有辦法走下去,AutoCAD DWG檔案簡直就是一個複雜的巨型圖層資料庫而且每兩年改版一次,建議還是用DXF圖檔相容性較高,有機會再談談DXF檔結構。這裡先分享一下DWG解析方法給有興趣研發的好友參考一 下。
基本上程式要解析其中的資料結構如下,有點長刪減一些函數但原理不變。
讀取DWG檔後打開資料庫取實體EntityName的字串名稱(約50個標籤名稱)一層層解析後繪圖即可原圖重現,看你要加圖、刪圖或送去產生G-Code等,另外如果用不著的功能就略過,沒必要全部實作,有興趣可一起討論,下回再討論G-Code產生器。
p.s. 下列範例是C#版本,有興趣的朋友歡迎自行改成Python版本。
//------------------------------------------------------------------- public void Draw(List<CadParse> cadparselist) { foreach(CadParse cadParse in cadparselist) { switch(cadParse.EntityName) { case "AcDbAlignedDimension": break; case "AcDbArc": break; case "AcDbArcDimension": break; case "AcDbBlockReference": break; case "AcDbBody": break; case "AcDbCircle": DrawCircle(cadParse); break; case "AcDbPoint": break; case "AcDbDiametricDimension": break; case "AcDbViewport": break; case "AcDbEllipse": DrawEllipse (cadParse); break; case "AcDbFace": break; case "AcDbHatch": break; case "AcDbLeader": break; case "AcDbLine": DrawLine(cadParse); break; case "AcDb2LineAngularDimension": break; case "AcDbMInsertBlock": break; case "AcDbMline": break; case "AcDbMText": break; case "AcDbOle2Frame": break; case "AcDbOrdinateDimension": break; case "AcDb3PointAngularDimension": break; case "AcDbPolyFaceMesh": break; case "AcDbPolygonMesh": break; case "AcDbPolyline": DrawPolyLine(cadParse); break; case "AcDb2dPolyline": break; case "AcDb3dPolyline": break; case "AcDbProxyEntity": break; case "AcDbRadialDimension": break; case "AcDbRasterImage": break; case "AcDbRay": break; case "AcDbRegion": break; case "AcDbRotatedDimension": break; case "AcDbShape": break; case "AcDbSolid": break; case "AcDb3dSolid": break; case "AcDbSpline": break; case "AcDbTable": break; case "AcDbTrace": break; case "AcDbWipeout": break; case "AcDbXline": break; case "AcDbPdfReference": break; case "AcDbDwfReference": break; case "AcDbDgnReference": break; } //end switch(cadParse.EntityName) } //end foreach(CadParse cadParse in _cadParseList) } //end public void Draw()
原文網址 石小川 2019-11-26 11:24:33
(之前在其他社團分享過C#版本, 現在改用Python分享給社團好友。) 這個羅技飛行搖桿本來是用在一個從頭城出發要去一個遙遠地方的大台UAV,看到挑戰國內法令的罰款可能會罰到脫褲子, 所以只能收手不玩了, 天空不能玩了, 就換地上及海上囉, 於是把程式移植到CNC, 運作起來還蠻順手的, 像是 夾娃娃機, 其實不管是CNC軸控、太空船、飛機或機器人, Joystick程式是一樣的。 以前玩Apple-II時曾經用6502組合語言改寫地球保衛戰的電玩, 大意是太空衛星發射雷射光射向入侵地球的飛碟, 但不可以射到底下建築物否則扣分, 這個CNC Demo頗有幾分神似呢, 特別是雷射扳機打開發射時, 感覺還蠻爽的^^ 下面是控制搖桿程式庫TJoystick, 分享給社團好友參考一下, 須自行想辦法最佳化, 可很容易地加入自已的機台或UAV。 因為主程式很長, 所以只列出TJoystick程式庫, 主程式就不po了, 有興趣再私下討論了。 當然, 眾所皆知, Python在資料處理方面的確非常方便, 對科學家來說可以省下很多Coding 時間, 有機會再分享Python 在光學雷達(Lidar)及數位訊號處裡(DSP)的應用實例和程式碼。 p.s. Python在將來關燈工廠會佔有一席之地, 事實上在我專案計畫裡有個1024顆Xilinx FPGA迷你超級電腦是用Python當介面。 如果對Python自動控制有興趣的朋友可上我臉書或私訊我, 我有開些課程, 參考看看是否有幫助, 光騰臉書 : https://www.facebook.com/qmlab/ 。 #須先pip install pygame #引用搖桿模組 import pygame #------------------------------------------------------------------------------ #列舉搖桿的手柄動作型態 #------------------------------------------------------------------------------ class __JoystickDirectionType(): Center = 0 zUp = 1 zDown = 2 Up = 3 Right = 4 Down = 5 Left = 6 JoystickDirectionType = __JoystickDirectionType() #------------------------------------------------------------------------------ #搖桿控制類別 #------------------------------------------------------------------------------ class TJoystick: #------------------------------------------------------------------------ def __init__(self): self.joystickCount = 0 #搖桿數目 ,接了幾支搖桿。 self.numberButtons = 0 #按鍵數目 #串列 [ ] ,用來儲存搜尋到的搖桿名稱,一隻就一個名稱。 self.joysticks = [] self.pjoystick = None #指向選用的搖桿位址 #字典型態 { },用來儲存搖桿手柄三軸及按鍵的目前狀態。 self.Controls = { 'Trigger' : 0, # Button_0 'Axis-X' : 0, 'Axis-Y' : 0, 'Axis-Z' : 0, 'Hat-X' : 0, 'Hat-Y' : 0, 'Slider' : 0, 'Button-0' : 0, 'Button-1' : 0, 'Button-2' : 0, 'Button-3' : 0, 'Button-4' : 0, 'Button-5' : 0, 'Button-6' : 0, 'Button-7' : 0, 'Button-8' : 0, 'Button-9' : 0, 'Button-10' : 0, 'Button-11' : 0, 'Button-12' : 0 } #------------------------------------------------------------------------ #開啟搖桿設備 def Open(self): #顯示初始化設定 , 沒使用到, 但必須有這一項, 否則無法正常執行 #Console : error: video system not initialized訊息不用理會 pygame.display.init() #搖桿初始化設定 pygame.joystick.init() #取得幾隻搖桿 self.joystickCount = pygame.joystick.get_count() #print (self.joystickCount) #串列 [pointer, name, nameaxes, numbuttons, umhats], 用來暫存一個搖桿資訊。 joystick = [] #找到的搖桿名稱就放進joystick for i in range(self.joystickCount): pjoystick = pygame.joystick.Joystick(i) pjoystick.init() pygame.joystick.init() #暫存取得的一個搖桿資訊 [pointer, name, nameaxes, numbuttons, umhats] joystick.append(pjoystick) #pointer [0] joystick.append(pjoystick.get_name()) #name [1] joystick.append(pjoystick.get_numaxes()) #numaxes [2] joystick.append(pjoystick.get_numbuttons()) #numbuttons [3] joystick.append(pjoystick.get_numhats()) #numhats [4] #[pointer, name, nameaxes, numbuttons, umhats] self.joysticks.append(joystick) #指向第一個Joystick (因為只接一個搖桿) self.pjoystick = self.joysticks[0][0] self.numberButtons = pjoystick.get_numbuttons(); #取得按鍵數目 #假如沒接搖桿則傳回False if(self.joystickCount == 0): return False #正確傳回True return True #------------------------------------------------------------------------ #關閉搖桿設備 def Close(self): pygame.quit() #------------------------------------------------------------------------ #取得搖桿資訊 def JoystickInfo(self): return self.joysticks #------------------------------------------------------------------------ #取得搖桿目前所有狀態 def ControlsInfo(self): return self.Controls #------------------------------------------------------------------------ #取得搖桿目前Trigger鍵狀態 (同Button-0), 所有Button 壓下去 = 1, 放掉 = 0 def getTrigger(self): return self.Controls['Trigger'] #------------------------------------------------------------------------ #取得搖桿目前Button -0鍵狀態(同Trigger) def getButton0(self): return self.Controls['Button-0'] #------------------------------------------------------------------------ #取得搖桿目前Button -1鍵狀態 def getButton1(self): return self.Controls['Button-1'] #------------------------------------------------------------------------ #取得搖桿目前Button -2鍵狀態 def getButton2(self): return self.Controls['Button-2'] #------------------------------------------------------------------------ #取得搖桿目前Button -3鍵狀態 def getButton3(self): return self.Controls['Button-3'] #------------------------------------------------------------------------ #取得搖桿目前Button -4鍵狀態 def getButton4(self): return self.Controls['Button-4'] #------------------------------------------------------------------------ #取得搖桿目前Button -5鍵狀態 def getButton5(self): return self.Controls['Button-5'] #------------------------------------------------------------------------ #取得搖桿目前Button -6鍵狀態 def getButton6(self): return self.Controls['Button-6'] #------------------------------------------------------------------------ #取得搖桿目前Button -7鍵狀態 def getButton7(self): return self.Controls['Button-7'] #------------------------------------------------------------------------ #取得搖桿目前Button -8鍵狀態 def getButton8(self): return self.Controls['Button-8'] #------------------------------------------------------------------------ #取得搖桿目前Button -9鍵狀態 def getButton9(self): return self.Controls['Button-9'] #------------------------------------------------------------------------ #取得搖桿目前Button -10鍵狀態 def getButton10(self): return self.Controls['Button-10'] #------------------------------------------------------------------------ #取得搖桿目前Button -11鍵狀態 def getButton11(self): return self.Controls['Button-11'] #------------------------------------------------------------------------ #取得搖桿目前Button -12鍵狀態 def getButton12(self): return self.Controls['Button-12'] #------------------------------------------------------------------------ #取得搖桿目前Axis -X軸值 def getAxisX(self): return self.Controls['Axis-X'] #------------------------------------------------------------------------ #取得搖桿目前Axis -Y軸值 def getAxisY(self): return self.Controls['Axis-Y'] #------------------------------------------------------------------------ #取得搖桿目前Axis -Z軸值 def getAxisZ(self): return self.Controls['Axis-Z'] #------------------------------------------------------------------------ #取得搖桿目前Slider軸值 def getSlider(self): return self.Controls['Slider'] #------------------------------------------------------------------------ #取得搖桿目前Hat -X軸狀態 def getHatX(self): return self.Controls['Hat-X'] #------------------------------------------------------------------------ #取得搖桿目前Hat -Y軸值 def getHatY(self): return self.Controls['Hat-Y'] #------------------------------------------------------------------------ #同時取得搖桿目前Hat -X軸, Hat-Y軸值 def getHatXY(self): return self.Controls['Hat-X'], self.Controls['Hat-Y'] #------------------------------------------------------------------------ #輪循監聽搖桿所有狀態 , 此函數應該放在執行緒中執行 def poll(self): #取得搖桿事件並放入事件堆疊中 , 此函數必須放置此處 pygame.event.pump() #將偵測到的搖桿狀態儲存至字典self .Controls for i in range(self.numberButtons): self.Controls['Button-'+str(i)] = self.pjoystick.get_button(i) self.Controls['Axis-X'] = self.pjoystick.get_axis(0) self.Controls['Axis-Y'] = self.pjoystick.get_axis(1) self.Controls['Slider'] = self.pjoystick.get_axis(2) self.Controls['Axis-Z'] = self.pjoystick.get_axis(3) self.Controls['Hat-X'] = self.pjoystick.get_hat(0)[0] self.Controls['Hat-Y'] = self.pjoystick.get_hat(0)[1] self.Controls['Trigger'] = self.pjoystick.get_button(0)
Grass Lin 2019-11-26 13:20:29
原文網址 石小川 2019-11-26 10:25:02
有蠻多朋友問我一個問題 - 學完Python程式之後就不知要幹嘛? 在此我提出我的看法, 看是否對初學者有幫助。
程式語言重要但只是一個輔助工具而已、就像英文一樣,不要模糊了主軸,最重要的還是人,建議找一個專題投入你的想像力和創造力, 程式功力自然提升,到時就可理解Python除了可做AI專家系統推論機、解藥廠化學式、高能物理學方程式外,還可做到雷達\聲納訊號分析或控制機器人....等等。與此跟大家共勉之。
後續會提出一些軟/硬體專題提供大家參考,希望有興趣的朋友將Python + PyQt5基礎先預習一下跟上腳步,以後軟硬體都會用Python + PyQt5來實作。
圖中操作面板就是以PyQt5實作UI介面。
p.s. 這幾天會將以前散布在各個社團的舊文章及程式整合起來以方便查閱,歡迎沒看過的朋友閱覽
陳必凱 2019-11-26 10:49:05
Grass Lin 2019-11-26 12:21:37
Grass Lin 2019-11-26 12:21:46
感謝分享
侯凱亮 2019-11-26 15:08:41
感恩!
全不選 發文排行