文章目录

  • 前言
  • 一、pyqt5和vtk是什么?
  • 二、基本原理
  • 三、实现步骤
    • 1.在pycharm上安装所需库
    • 2.随意写一个pyqt5界面类并定义预览控件的初始化方法
    • 3.在窗口中加入预览台
    • 4.解析gcode文件
    • 5.将分层位置信息转化为分层面片信息
    • 6.将分层面片信息根据颜色信息进行渲染
    • 7.清空界面,根据解析出的模型中心位置重新添加预览台,分层叠加渲染结果
  • 四、结果展示
  • 五、部分说明
  • PS:

前言

 3D打印切片控制一体化流程中涉及到的文件格式主要有stl以及gcode文件。stl文件为三维实体模型,其预览功能的实现较为成熟,对于java和python而言,通常可以运用openGL库实现预览,而gcode文件为切片处理后生成的文件,本质上与txt等文本文件类似,不包含stl文件中的面片信息,因此实现gcode文件的预览需要对其文件进行解析,实现过程较为复杂,相关资料较少,本文在实现gcode基本预览的基础上,基于marlin固件的M165指令完成gcode模型的全彩预览,主要涉及到的编程语言为python,涉及到的第三方库主要有pyqt5和vtk。

一、pyqt5和vtk是什么?

 在进行开发时,后台实现业务逻辑,前台负责界面展示,网络编程通常使用html+css+JavaScript的方式完成前端界面的展示,利用AS进行过Android开发的人通常使用xml文件完成前端界面。如果不想使用类似flask这样的web框架进行网络编程,只希望做一个简单的exe文件方便自己的日常使用,当然也可以用纯python进行界面制作,PyQt5 就是这样一种具有强大功能的GUI库之一。
 vtk是一种图形库,主要用于三维计算机图形学、图像处理和可视化。Vtk是在面向对象原理的基础上设计和实现的,它的内核采用C++构建,但同样可以运用在python以及Java中,stl预览以及gcode预览都可以基于此库实现。

二、基本原理

三、实现步骤

1.在pycharm上安装所需库

python解释器安装库(画圈为预览所必须的库):

2.随意写一个pyqt5界面类并定义预览控件的初始化方法

代码如下(记得先import相关库):

    def init3dWidget(self):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)#创建坐标轴return widget3d

 初始化方法的意义在于规定预览窗口的背景颜色以及坐标轴等基础元素,至于预览窗口的大小可以先利用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()

下面两个函数在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])#中心mapper = vtk.vtkPolyDataMapper()mapper.SetInputConnection(cylinder.GetOutputPort())actor = vtk.vtkActor()actor.SetMapper(mapper)actor.GetProperty().SetColor(params.PlaneColor)#颜色actor.RotateX(90)return actor

 完成以上两步就能看到初始化界面窗口和预览台,如下图所示

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 = []#颜色信息floor = 0#层数divide = []#分层信息center = None#模型中心位置plane = []#只有xyz坐标值stop = Falserotations.append(Rotation(0, 0))x, y, z = 0, 0, 0w,e,r = 0,0,0abs_pos = True  # absolute positioningdef finishLayer():nonlocal path, layerif 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:continueif line.startswith(";LAYER:1"):stop = Trueif 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只有一个值center = [w, e, r]else:if "G1" in line or "G0" in line:a = float(getValue(line, "X", -1))b = float(getValue(line, "Y", -1))c = float(getValue(line, "Z", -1))if a != -1 and b != -1 and c != -1:plane.append([a, -b,-c])else:plane.append([a, -b, 0])args = line.split(" ")if args[0] == "G0":  if len(path) > 1:  # finish path and start newlayer.append(path)x, y, z, z_rot = parseArgs(args[1:], x, y, z, abs_pos)path = [[x, y, z]]if z_rot is not 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))color.append(material_chose(a,b,c))print(color)elif args[0] == "G1" :  # draw tox, y, z, _ = parseArgs(args[1:], x, y, z, abs_pos)path.append([x, y, z])elif args[0] == "G90":  # absolute positioningabs_pos = Trueelif args[0] == "G91":  # relative positioningabs_pos = Falseelse:pass  # skip# finishLayer()  # not forget about last layerdivide.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)#每一层单独存储
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},{'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},{'C':70, 'M':20, 'Y':10, 'R':141, 'G':111, 'B':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': 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': 40, 'M': 20, 'Y': 40, 'R': 171, 'G': 129, 'B': 83},{'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:if  user_a == i['C'] and user_b == i['M'] and user_c == i['Y']:color = [i['R'],i['G'],i['B']]if len(color) == 0:#如果固定色卡中没有则利用公式进行转换total = user_a + user_b + user_ccolor = [255-255*user_a/total,255-255*user_b/total,255-255*user_c/total]return color

5.将分层位置信息转化为分层面片信息

def makeBlocks(layers):   #  将离散点变成面片信息blocks = []for layer in layers:points = vtk.vtkPoints()    #  点云lines = vtk.vtkCellArray()block = vtk.vtkPolyData()points_count = 0for path in layer: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)points.InsertNextPoint(path[-1])  # not forget to add last pointpoints_count += len(path)block.SetPoints(points)block.SetLines(lines)blocks.append(block)return blocks

 此函数传入的layers对应着解析函数中layers,此函数主要将点信息转化为线信息再转化为面片信息,接着就可以这些分层面片信息赋予颜色并转化分层实体模型。

6.将分层面片信息根据颜色信息进行渲染

def wrapWithActors(blocks, rotations, lays2rots,color = None,divide = None):actors = []for i in range(len(blocks)):block = blocks[i]actor = build_actor(block, True)transform = vtk.vtkTransform()# rotate to abs coords firstly and then apply last rotationtransform.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)actor.SetUserTransform(transform)if len(color) == 0: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]:actors[layer + 1].GetProperty().SetColor(color[i][0]/255,color[i][1]/255,color[i][2]/255)else:for layer in range(divide[i+1],len(actors)-1):actors[layer + 1].GetProperty().SetColor(color[i][0] / 255, color[i][1] / 255,color[i][2] / 255)print(len(actors))actors[-1].GetProperty().SetColor(params.LayerColor)return actors
def build_actor(source, as_is=False):#source就是面片信息,gcode需要得到面片信息mapper = vtk.vtkPolyDataMapper()# 2. 建图(将点拼接成立方体)if as_is:mapper.SetInputData(source)#gcode信息需要用另外的方法提取else:mapper.SetInputData(source.GetOutput())#提取解析stl结果的面片信息actor = vtk.vtkActor()actor.SetMapper(mapper)# 3. 根据2创建执行单元return actor

7.清空界面,根据解析出的模型中心位置重新添加预览台,分层叠加渲染结果

self.clearScene()#清空预览台
self.planeActor = gui_utils.createPlaneActorCircle(self.gode.center)
self.render.AddActor(self.planeActor)#自适应生成符合模型位置的预览台
for actor in self.actors:#分层叠加渲染结果self.render.AddActor(actor)

四、结果展示

渐变模型:

分层模型:

单色模型:

五、部分说明

  1. 上述解析函数只适用于cura切出的相关模型,如果想兼容预览3r切出的gcode文件,还需在文件导入后进行相关预处理,不仅需要添加类似layer;这样的标识符便于分层解析,也需要将3r文件中跳转点的G1指令转化为与cura相同的G0指令。
  2. 本文的颜色信息的解析主要依赖于marlin固件提供的M165指令,此指令类似M165 A0.8733 B0.0188 C0.1079,其中ABC对应的是挤出机对应的挤料比例,本人使用的三喷头采用的是cmy三原色,即青色(Cyan)、品红(Magenta)和黄色(Yellow)。
  3. 使用vtk库同样可以完成stl模型的三维预览,预览形式如下图所示,代码实现在本文就不多赘述。
  4. 由于现在主流的cura以及3r切片软件无法切出含有M165指令的gcode,因此需要进行相关的gcode后处理实现M165指令的插入,单色、分层以及渐变的gcode后处理方法会再写一篇博客展示。

PS:

第一次写博客,水平有限,有什么问题可以联系本尊,扣扣:408536802,谢谢!

利用python的pyqt5和vtk库实现对gcode模型的全彩预览相关推荐

  1. 利用Python进行百度文库内容爬取(二)——自动点击预览全文并爬取

    本文是衔接上一篇:<利用Python进行百度文库内容爬取(一)>. 上回说到我们在对百度文库进行爬虫时,需要模拟手机端来进行登录,这样固然可以对文章进行爬取,但是很多时候并不是非常智能的翻 ...

  2. 基于Python的高校勤工俭学工资管理系统——实现对excel表格的数据操作(xwlings库)

    基于Python的高校勤工俭学工资管理系统 1 需求概述 1.1 需求分析 勤工俭学是指学校组织的或学生个人从事的有酬劳动,用以助学.在我国,许多高校借以对学生进行劳动技术教育,培养正确的劳动观点和态 ...

  3. 用python的openpyxl库实现对excel工作表的自动化操作

    用python的openpyxl库实现对excel工作表的自动化操作 用python的openpyxl库读取excel工作表,批量建立工作表,批量修改工作表标题,批量设置单元格样式,批量调整打印设置. ...

  4. 使用go语言GUI库实现对mp3文件的播放1(简单的播放mp3文件)

    使用go语言GUI库实现对mp3文件的播放1(简单的播放mp3文件) 使用beep播放mp3文件(10num) 使用go语言GUI库fyne实现音乐播放器 要是想使用go语言实现播放mp3需要借助be ...

  5. pyqt5如何循环遍历控件名_利用Python的PyQt5编写GUI界面教学,QT5还是比较难的

    由于学校课程的项目,最近最近在学习如何利用Python语言和SQL Server编写一个读者图书借阅.查询管理的小程序.以此为契机吧,自己便开始了学习之路~ 这篇文章主要介绍自己如何使用PyQt5编写 ...

  6. python【PyQt5】的环境搭建和使用(全网最全)其一

    目录 什么是pyQT 为什么要开发桌面应用 要讲些什么 搭建PyQt5开发环境 参见CSDN:pyQt5环境的搭建_Hi~晴天大圣的博客-CSDN博客_pyqt5环境搭建 1 Python 2 PyQ ...

  7. android 生成 资源文件,SVG-Android开源库——SVG生成Vector资源文件的编辑预览工具...

    Vector矢量图在Android项目中的利用愈来愈广泛,但是如果你想用Android Studio自带的工具将SVG图片转化成Vector资源文件却是相当麻烦,首先能支持的SVG规范较少,其次操作流 ...

  8. 一款Android图片预览的开源库,几乎百分百还原微信的图片预览。

    图片预览组件PreviewPictureView gihub地址:https://github.com/OneZeroYang/PreviewPictureView 几乎还原微信的图片预览,核心使用共 ...

  9. Python之利用PyPDF2库实现对PDF的删除和合并

    文章目录 概述 安装 一.The PdfFileReader Class 1.getNumPages() 2.getPage(pageNumber) 二.The PdfFileWriter Class ...

  10. 利用Python的PIL、PyMuPDF库为图片和PDF文件去水印

    目标:净化电子书水印,供notability使用. 库安装 pillow库:基于PIL,支持python3 官方文档:Pillow (PIL Fork) 9.2.0.dev0 documentatio ...

最新文章

  1. 用PaddlePaddle打比赛!
  2. linux更新模块,Linux下Nginx的安装、升级及动态添加模块
  3. 获取人口_「微科普」14亿人口数据是如何得到的?
  4. Javascript实现BF算法
  5. element ui分页怎么做_vue+element-ui的分页完整版
  6. 15.Numpy之点乘、算术运算、切片、遍历和下标取值
  7. C#winform遍历控件判断控件类型
  8. 电脑族每天宜喝四杯茶
  9. Ubuntu 下为 Idea 创建启动图标.
  10. YML格式(java 常用配置文件格式)
  11. mysql 检查配置_MYSQL 配置检查脚本
  12. numpy的argsort和sum和tile函数
  13. “双评价”——ArcGIS水资源评价
  14. hutool SpringUtil.getBean() NullPointerException
  15. 单片机串口连接电脑,USB转TTL线的使用
  16. c语言字符串怎么退位,C语言第五六次作业.ppt
  17. matlab求梯度的原理,matlab 梯度计算原理
  18. Java开发环境基础配置
  19. Python实现输出手写体图片
  20. 广告联盟介绍之——A5广告联盟

热门文章

  1. c51单片机模块化编程
  2. jQuery版本低引起的漏洞——CVE-2020-11022/CVE-2020-11023
  3. QT 字体家族中的 字体名称中英文名称对应
  4. 网易云音乐 网络错误 linux,archlinux下网易云音乐netease-cloud-music启动报错,无法打开...
  5. 高速PCB设计规范(二)
  6. 马尔科夫随机场 matlab,matlab马尔可夫随机场
  7. YOLO v2详细解读
  8. YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5的发展(1)
  9. 计算机程序设计流程图循环,流程图循环画法_流程图用什么办公软件
  10. HTML页面跳转的方法