pythonのpyqt5およびvtkライブラリを使用して、gcodeモデルのフルカラープレビューを実現します

序文

 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、いくつかの説明

  1. 上記の分析機能は、curaによって切り出された関連モデルにのみ適用されます.3rによって切り出されたgcodeファイルのプレビューと互換性を持たせたい場合は、ファイルのインポート後に関連する前処理を実行する必要があります。同様のレイヤーを追加する必要があるだけでなく、このような識別子は階層分析に便利です。また、3rファイルのジャンプポイントにあるG1命令をcuraと同じG0命令に変換する必要があります。
  2. この記事の色情報の分析は、主にmarlinファームウェアによって提供されるM165コマンドに依存しています。これはM165 A0.8733 B0.0188 C0.1079に類似しています。ここで、ABCは押出機に対応する押出比に対応し、私が使用する3つのノズルはcmyの3つの主要な色、つまりシアン、マゼンタ、イエローです。
  3. stlモデルの3Dプレビューは、vtkライブラリを使用して完了することもできます。プレビュー形式を次の図に示します。
  4. 現在の主流のキュラと3rスライシングソフトウェアはM165命令を含むgcodeを切り出すことができないため、M165命令の挿入を実現するには、関連するgcode後処理を実行する必要があります。モノクロ、レイヤード、段階的なgcode後処理方法は別のブログを作成します公演。

完全なプロジェクトコード入手するには、ここをクリックしてください

おすすめ

転載: blog.csdn.net/weixin_43881394/article/details/108996065