序文
3D印刷スライス制御の統合プロセスに関係するファイル形式には、主にstlファイルとgcodeファイルが含まれます。stlファイルは3次元のソリッドモデルであり、そのプレビュー機能は比較的成熟しています。javaおよびpythonの場合、通常、openGLライブラリを使用してプレビューを実行できますが、gcodeファイルはスライス後に生成されるファイルであり、基本的にtxtなどのテキストファイルと同様です。 stlファイルにパッチ情報が含まれていないため、gcodeファイルのプレビューを解析する必要があり、実装プロセスがより複雑になり、関連情報が少なくなります。この記事は、marlinファームウェアのM165命令に基づくgcodeの基本的なプレビューに基づいています。 gcodeモデルのフルカラープレビューには主にプログラミング言語pythonが含まれ、関連するサードパーティライブラリには主にpyqt5とvtkが含まれます。
1. pyqt5とvtkとは何ですか?
開発中、バックエンドはビジネスロジックを実装し、フロントデスクはインターフェイスの表示を担当します。ネットワークプログラミングでは通常、html + css + JavaScriptを使用してフロントエンドインターフェイスの表示を完了します。ASforAndroid開発を使用したことがある人は、通常xmlファイルを使用してフロントエンドインターフェイスを完了します。フラスコのようなWebフレームワークをネットワークプログラミングに使用したくない場合は、日常的に使用するための単純なexeファイルを作成するだけです。もちろん、インターフェイスの作成に純粋なpythonを使用することもできます。PyQt5はそのような強力なGUIライブラリの1つです。 。
vtkはグラフィックライブラリであり、主に3次元のコンピュータグラフィック、画像処理、および視覚化に使用されます。Vtkは、オブジェクト指向の原則に基づいて設計および実装されています。そのコアはC ++で構築されていますが、pythonおよびJavaでも使用できます。stlプレビューとgcodeプレビューの両方をこのライブラリに基づいて実装できます。
2.基本原則
3つの実装手順
1.必要なライブラリをpycharmにインストールします
Pythonインタープリターインストールライブラリ(円はプレビューに必要なライブラリです):
2. pyqt5インターフェイスクラスを自由に記述し、プレビューコントロールの初期化メソッドを定義します。
コードは次のとおりです(最初に関連するライブラリをインポートすることを忘れないでください)。
DEF init3dWidget(自己): widget3d = QVTKRenderWindowInteractor() widget3d.Initialize() widget3d.Start() self.render = vtk.vtkRenderer() self.render.SetBackground(params.BackgroundColor)#设置背景颜色 widget3d.GetRenderWindow()。 AddRenderer(self.render) self.interactor = widget3d.GetRenderWindow()。GetInteractor() self.interactor.GetInteractorStyle()。SetCurrentStyleToTrackballCamera() self.axesWidget = gui_utils.createAxes(self.interactor)#创建坐png 轴returnwidget3d
初期化方法の重要性は、プレビューウィンドウの背景色や座標軸などの基本要素を指定することです。プレビューウィンドウのサイズについては、まずpyqt5の関連レイアウトを使用してサイズを決定し、次にaddWidgetメソッドを使用して上記の初期化方法を転送して基本プレビューウィンドウの作成を完了します。
self.main_grid.addWidget(self.init3dWidget())
3.ウィンドウにプレビューステーションを追加します
コードは次のとおりです(例)。
self.planeActor = gui_utils.createPlaneActorCircle(params.PlaneCenter)#例としてシリンダーを 取り上げますself.planeTransform = vtk.vtkTransform() self.render.AddActor(self.planeActor) self.render.ResetCamera()
次の2つの関数がgui_utils.pyにあります。このファイルは、主に一般的な関数を定義します。
def createPlaneActorCircle(x): return createPlaneActorCircleByCenter(x) def createPlaneActorCircleByCenter(center): cylinder = vtk.vtkCylinderSource()#Cylinder.SetResolution (50) cylinder.SetRadius(params.PlaneDiameter / 2)#大小 cylinder.SetHeight(0.1) #高度 cylinder.SetCenter(center [0]、center [2] -0.1、center [1])#中心 マッパー= vtk.vtkPolyDataMapper() mapper.SetInputConnection(cylinder.GetOutputPort()) actor = vtk.vtkActor() actor .SetMapper(mapper) actor.GetProperty()。SetColor(params.PlaneColor)#颜色 actor.RotateX(90) リターンアクター
上記の2つの手順を完了すると、次の図に示すように、初期化インターフェイスウィンドウとプレビュープラットフォームが表示されます。
4.gcodeファイルを解析します
表示ウィンドウが完成したら、gcodeのフルカラープレビューの背景をさらに改善できます。これを行うには、最初にボタンコントロールを設定し、pyqt5が提供するQFileDialog.getOpenFileNameメソッドを使用してgcodeファイル選択ウィンドウを作成し、gcodeパスを解析関数に渡す必要があります。
解析コードは次のとおりです(例)。
def readGCode(filename): with open(filename)as f: lines = [line.strip()for line in f] return parseGCode(lines) def parseGCode(lines): path = [] layer = [] layers = [] #位置情報と回転情報 rotations = [] lays2rots = [] color = []#色信息床= 0#レイヤー番号 除算= [] #レイヤー情報 センター=なし#モデル中心位置 平面= [] #xyz座標のみ値 stop = Falserotations.append (Rotation(0、0)) x、y、z = 0、0、0 w、e、r = 0,0,0 abs_pos = True#絶対位置 def finishLayer(): パス、レイヤー if len(path)> 1: layer.append(path) path = [[x、y、z]] if len(layer)> 0: layers.append(layer) lays2rots.append(len(rotations)-1) layer = [] for line in lines : if len(line)== 0: 続行 if line.startswith( "; LAYER:1"): stop = True if line.startswith( ';'): if line.startswith( "; LAYER:"): finishLayer () floor + = 1 #elif line.startswith( "; End"): #break #if line.startswith( "; LAYER:"): if stop is True: for i in range(len(plane)): w + = plane [i] [0] e + = plane [i] [1] r + = plane [i] [2] w = w / len(plane) e = e / len(plane) r = r / len(plane) stop = False#归位 if center is None:#保证center只有一OT值 center = [w、e、r] else: if "行のG1 "または行の" G0 ": a = float(getValue(line、" X "、-1)) b = float(getValue(line、" Y "、-1)) c = float(getValue(line 、 "Z"、-1)) a!= -1およびb!= -1およびc!= -1: plane.append([a、-b、-c]) else: plane.append([a、-b、0]) args = line.split( "") if args [0] == "G0": if len(path)> 1:#パスを終了して新しい レイヤーを開始します。append(path) x、y、z、z_rot = parseArgs(args [1:]、x、y、z、abs_pos) path = [[x、y 、z]] z_rotがNoneでない場合: finishLayer() rotations.append(Rotation(rotations [-1] .x_rot、z_rot)) elif args [0] == "M165":#全彩解析 divide.append(floor ) a = float(getValue(line、 "A"、-1)) b = float(getValue(line、 "B"、-1)) c = float(getValue(line、 "C"、-1)) 色。 append(material_chose(a、b、c)) print(color) elif args [0] == "G1":# x、y、zに描画、_ = parseArgs(args [1:]、x、y、z 、abs_pos) path.append([x、y、z]) elif args [0] == "G90":#絶対位置 abs_pos = True elif args [0] == "G91":#相対位置 abs_pos = False else : 合格#スキップ #finishLayer()#最後のレイヤーを忘れないでください divide.append(floor) print(len(divide)、floor、len(color)、center) layers.append(layer) lays2rots.append(len(rotations)-1) # print(layers) return GCode(layers、rotations、lays2rots、color、divide、center)#每一層単専储 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828
def material_chose(user_a、user_b、user_c): "" "挤料比例模块" "" color = [] info = [{'C':98、 'M':1、 'Y':1、 'R': 128、 'G':199、 'B':217}、 {'C':89、 'M':10、 'Y':1、 'R':153、 'G':185、 'B': 215}、 {'C':79、 'M':20、 'Y':1、 'R':160、 'G':144、 'B':191}、 {'C':69、 'M ':30、' Y ':1、' R ':172、' G ':136、' B ':188}、 {' C ':59、' M ':40、' Y ':1、' R ':175、' G ':112、' B ':172}、 {' C ':49、' M ':50、' Y ':1、'R ':179、' G ':94、' B ':161}、 {' C ':39、' M ':60、' Y ':1、' R ':190、' G ':92、' B ':165}、 {' C ':29、' M ':70、' Y ':1、' R ':195、' G ':93、' B ':167}、 {' C ':19 、 'M':80、 'Y':1、 'R':210、 'G':95、 'B':170}、B ':170}、B ':170}、 {'C':9、 'M':90、 'Y':1、 'R':225、 'G':104、 'B':181}、 {'C':1、 'M':98 、 'Y':1、 'R':244、 'G':104、 'B':193}、 {'C':1、 'M':89、 'Y':10、 'R':232 、 'G':112、 'B':163}、 {'C':1、 'M':79、 'Y':20、 'R':227、 'G':107、 'B':142 }、 {'C':1、 'M':69、 'Y':30、 'R':223、 'G':101、 'B':129}、 {'C':1、 'M' :59、 'Y':40、 'R':225、 'G':109、 'B':128}、 {'C':1、 'M':49、 'Y':50、 'R' :224、'G':118、 'B':127}、 {'C':1、 'M':39、 'Y':60、 'R':223、 'G':124、 'B':120} 、 {'C':1、 'M':29、 'Y':70、 'R':223、 'G':137、 'B':116}、 {'C':1、 'M': 19、「Y」:80、「R」:225、「G」:159、「B」:113}、 {'C':1、 'M':9、 'Y':90、 'R':226、 'G':179、 'B':96}、 {'C':1、 'M ':1、' Y ':98、' R ':223、' G ':215、' B ':89}、 {'C':10、 'M':1 、 'Y':89、 'R':214、 'G':213、 'B':88}、 {'C':20、 'M':1、 'Y':79、 'R':194 、 'G':211、 'B':105}、 {'C':30、 'M':1、 'Y':69、 'R':177、 'G':202、 'B':108 }、 {'C':40、 'M':1、 'Y':59、 'R':169、 'G':203、 'B':126}、 {'C':50、 'M' :1、 'Y':49、 'R':161、 'G':200、 'B':138}、 {'C':60、 'M':1、 'Y':39、 'R' :153、 'G':195、 'B':145}、 {'C':70、 'M':1、 'Y':29、 'R':140、 'G':191、 'B':154}、 {'C':80、 'M':1 、 'Y':19、 'R':141、 'G':197、 'B':168}、 {'C':90、 'M':1、 'Y':9、 'R':137 、 'G':199、 'B':190}、 {'C':80、 'M':10、 'Y':10、 'R':138、 'G':145、 'B':129 }、190}、 {'C':80、 'M':10、 'Y':10、 'R':138、 'G':145、 'B':129}、190}、 {'C':80、 'M':10、 'Y':10、 'R':138、 'G':145、 'B':129}、 {'C':70、 'M':20、 'Y':10、 'R':141 、 'G':111、 'B':113}、 {'C':60、 'M':30、 'Y':10、 'R':149、 'G':91、 'B':106 }、113}、 {'C':60、 'M':30、 'Y':10、 'R':149、 'G':91、 'B':106}、113}、 {'C':60、 'M':30、 'Y':10、 'R':149、 'G':91、 'B':106}、 {'C':50、 'M':40 、 'Y':10、 'R':154、 'G':71、 'B':94}、 {'C':40、 'M':50、 'Y':10、 'R':163 、 'G':63、 'B':89}、 {'C':30、 'M':60、 'Y':10、 'R':168、 'G':54、 'B':80 }、 {'C':20、 'M':70、 'Y':10、 'R':178、 'G':50、 'B':77}、 {'C':10、 'M' :80、 'Y':10、 'R':187、 'G':44、 'B':77}、 {'C':70、 'M':10、 'Y':20、 'R' :152、 'G':150、 'B':110}、 {'C':60、 'M':20、 'Y':20、 'R':158、 'G':131、 'B':117}、 {'C':50、 'M':30 、 'Y':20、 'R':173、 'G':120、 'B':118}、 {'C':40、 'M':40、 'Y':20、 'R':181 、 'G':110、 'B':116}、 {'C':30、 'M':50、 'Y':20、 'R':177、 'G':88、 'B':98}、 {'C':40、 'M ':20、' Y ':40、' R ':171、' G ':129、' B ':83}、 {'C':20、 'M':60、 'Y':20、 'R':187 、 'G':76、 'B':92}、 {'C':10、 'M':70、 'Y':20、 'R':193、 'G':69、 'B':90}、 {'C':60、 'M':10 、 'Y':30、 'R':160、 'G':161、 'B':114}、 {'C':50、 'M':20、 'Y':30、 'R':171 、 'G':131、 'B':106}、 {'C':40、 'M':30、 'Y':30、 'R':183、 'G':122、 'B':101 }、 {'C':30、 'M':40、 'Y':30、 'R':173、 'G':88、 'B':80}、 {'C':20、 'M' :50、 'Y':30、 'R':184、 'G':84、 'B':80}、 {'C':10、 'M':60、 'Y':30、 'R' :182、 'G':74、 'B':80}、 {'C':50、 'M':10、 'Y':40、 'R':158、 'G':155、 'B': 86}、 {'C':30、 'M':30、 'Y':40、 'R':178、 'G':104、 'B':78}、 {'C':20、 'M ':40、' Y ':40、' R ':182、' G ':93、' B ':70}、 {'C':10、 'M':50 、 'Y':40、 'R':192、 'G':82、 'B':72}、 {'C':40、 'M':10、 'Y':50、 'R':172 、 'G':152、 'B':65}、 {'C':30、 'M':20、 'Y':50、 'R':174、 'G':126、 'B':85 }、 {'C':20、 'M':30、 'Y':50、 'R':179、 'G':108、 'B':86}、 {'C':10、 'M' :40、 'Y':50、 'R':188、 'G':91、 'B':78}、 {'C':30、 'M':10、 'Y':60、 'R' :168、 'G':146、 'B':78}、 {'C':20、 'M':20、 'Y':60、 'R':179、 'G':120、 'B': 76}、 {'C':10、 'M':30、 'Y':60、 'R':190、 'G':104、 'B':74}、 {'C':20、 'M ':10、' Y ':70、' R ':187、' G ':149、' B ':65}、 {'C':10、 'M':20、 'Y':70、 'R':197、 'G':128、 'B':63}、 {'C':10、 'M ':10、' Y ':80、' R ':206、' G ':162、' B ':59}] for i in info:G ':162、' B ':59}] for i in info:G ':162、' B ':59}] for i in info: user_a == i ['C']およびuser_b == i ['M']およびuser_c == i ['Y']の場合: color = [i ['R']、i ['G']、i [ 'B']] if len(color)== 0: #固定カラーカードがない場合は、式を使用して合計= user_a + user_b + user_c color = [255-255 * user_a / total、255-255 * user_b /を変換します合計、255-255 * user_c / total] 戻り色
5.階層的な位置情報をスライスされた情報に変換します
def makeBlocks(layers):#将分離散点変面片信息 blocks = [] レイヤー内のレイヤー: points = vtk.vtkPoints()#点 ✓lines= vtk.vtkCellArray() block = vtk.vtkPolyData() points_count = レイヤー内のパスの場合は0 : line = vtk.vtkLine() for k in range(len(path)-1): points.InsertNextPoint(path [k]) line.GetPointIds()。SetId(0、points_count + k) line .GetPointIds()。SetId(1、points_count + k + 1) lines.InsertNextCell(line) block.SetPoints(points) block.SetLines(lines) points.InsertNextPoint(path [-1])#最後のポイントを追加することを忘れないでください points_count + = len(path) blocks.append(block)はブロックを 返します
この関数によって渡されるレイヤーは、分析関数のレイヤーに対応します。この関数は、主にポイント情報をライン情報に変換し、次にパッチ情報に変換します。次に、レイヤー化された情報に色を付けて、レイヤードエンティティモデルに変換できます。
6.色情報に従ってスライス情報をレンダリングします
def wrapWithActors(blocks、rotations、lays2rots、color = None、divide = None): actors = [] for i in range(len(blocks)): block =ブロック[i] actor = build_actor(block、True) transform = vtk .vtkTransform() #最初に絶対座標に回転してから最後の回転を適用する transform.PostMultiply() transform.RotateX(-rotations [lays2rots [i]]。x_rot) transform.PostMultiply() transform.RotateZ(-rotations [lays2rots [i ]]。z_rot) transform.PostMultiply() transform.RotateZ(rotations [-1] .z_rot) transform.PostMultiply() transform.RotateX(rotations [-1] .x_rot) len(color)== 0の場合 actor.SetUserTransform(transform): actor.GetProperty()。SetColor(params.LastLayerColor) elif len(color)== 1: actor.GetProperty()。SetColor(color [0] [0 ] / 255、color [0] [1] / 255、color [0] [2] / 255) actors.append(actor) if len(color)> 1: if len(color)== len(actors)- 2: for i in range(len(color)): actors [i] .GetProperty()。SetColor(color [i] [0] / 255、color [i] [1] / 255、color [i] [2 ] / 255) else: for i in range(len(color)): for layer in range(divide [i]、divide [i + 1]): if len(actors)-1> = divide [i + 1] : アクター[レイヤー+1] .GetProperty()。SetColor(color [i] [0] / 255、color [i] [1] / 255、color [i] [2] / 255) else: 範囲内のレイヤーの場合( split [i + 1]、len(actors)-1):actors [layer + 1] .GetProperty()。SetColor(color [i] [0] / 255、color [i] [1] / 255、color [ I] [2] / 255) 、印刷(LEN(アクター)) 俳優[-1] .GetProperty()。SETCOLOR(params.LayerColor) 戻り俳優 1234567891011121314151617181920212223242526272829303132333435363738
def build_actor(source、as_is = False):#sourceはパッチ情報です。gcodeはパッチ情報を取得する必要があります mapper = vtk.vtkPolyDataMapper()# 2。as_is: mapper.SetInputData(source )の場合、画像を作成します(ポイントをキューブに接続します)。)#gcode情報は、 elseを抽出するために別のメソッドを使用する必要があります: mapper.SetInputData(source.GetOutput())#解析されたstl結果のパッチ情報を抽出します actor = vtk.vtkActor() actor.SetMapper(mapper)#3.2に従って作成します実行ユニット リターンアクター
7.インターフェイスをクリアし、モデルの解析された中心位置に従ってプレビュープラットフォームを再度追加し、レンダリング結果をレイヤーにオーバーレイします
self.clearScene()#プレビュープラットフォームをクリアします self.planeActor = gui_utils.createPlaneActorCircle(self.gode.center) self.render.AddActor(self.planeActor)#self.actors 内のアクターのモデルの位置に一致するプレビュープラットフォームを適応的に生成します。 #レイヤードオーバーレイレンダリング結果self.render.AddActor(actor)
4.結果表示
勾配モデル:
階層モデル:
モノクロモデル:
5、いくつかの説明
- 上記の分析機能は、curaによって切り出された関連モデルにのみ適用されます.3rによって切り出されたgcodeファイルのプレビューと互換性を持たせたい場合は、ファイルのインポート後に関連する前処理を実行する必要があります。同様のレイヤーを追加する必要があるだけでなく、このような識別子は階層分析に便利です。また、3rファイルのジャンプポイントにあるG1命令をcuraと同じG0命令に変換する必要があります。
- この記事の色情報の分析は、主にmarlinファームウェアによって提供されるM165コマンドに依存しています。これはM165 A0.8733 B0.0188 C0.1079に類似しています。ここで、ABCは押出機に対応する押出比に対応し、私が使用する3つのノズルはcmyの3つの主要な色、つまりシアン、マゼンタ、イエローです。
- stlモデルの3Dプレビューは、vtkライブラリを使用して完了することもできます。プレビュー形式を次の図に示します。
- 現在の主流のキュラと3rスライシングソフトウェアはM165命令を含むgcodeを切り出すことができないため、M165命令の挿入を実現するには、関連するgcode後処理を実行する必要があります。モノクロ、レイヤード、段階的なgcode後処理方法は別のブログを作成します公演。
完全なプロジェクトコードを入手するには、ここをクリックしてください