Pyinstaller misses modules for packaging an OpenGL application - python

I am writing a PyQt5 application that uses OpenGL to slice an STL into images and displays them as it goes. The application works fine when invoked from the command line (Windows 10, Python 3.7.1, PyInstaller 3.5), but when I try packaging it with pyinstaller it crashes with an error:
Traceback (most recent call last):
File "app_qt.py", line 45, in initializeGL
self.gl = self.context().versionFunctions()
ModuleNotFoundError: No module named 'PyQt5._QOpenGLFunctions_4_1_Core'`
The call comes from a QtGui.QOpenGLWindow object. I have followed the suggestions in this answer to no avail. I have tried importing PyQt5 directly, and adding it to the hidden imports in the .spec file as well for pyinstaller.
Since the application runs fine when invoked normally (i.e. python slicer_gui.py) I am inclined to believe pyinstaller is neglecting to add an import to the package somewhere.
Here is the full code. The GUI is simple (pun intended):
import PySimpleGUI as sg
import app_pyopengl
# define GUI layout
layout = [
[sg.Text('STL to Slice', size=(16, 1)), sg.Input(), sg.FileBrowse()],
[sg.Text('Layer Thickness (um) ', size=(16, 1)), sg.InputText('10')],
[sg.Submit(button_text="Slice"), sg.Cancel(button_text='Quit')]
]
# name the window
window = sg.Window('PyQT STL Slicer').Layout(layout)
# loop until user quits
while True:
button, values = window.Read() # read all values in the window
if button == "Quit":
exit()
if button == "Slice":
thickness = float(values[1]) / 1000 # convert um to mm
filename = values[0]
app_pyopengl.main(filename, thickness)
Here is the slicing application:
import sys
import os
import shutil
from ctypes import c_float, c_uint, sizeof
from PyQt5 import QtGui, QtCore, QtWidgets
from stl import mesh
import numpy as np
from printer import printer
GLfloat = c_float
GLuint = c_uint
EPSILON = 0.00001
SCR_WIDTH = 640
SCR_HEIGHT = int(SCR_WIDTH * printer.height / printer.width)
class Window(QtGui.QOpenGLWindow):
def __init__(self,
stlFilename,
layerThickness,
sliceSavePath,
*args,
**kwargs):
super().__init__(*args, **kwargs)
self.setTitle('STL Slicer')
self.vertVAO, self.vertVBO = 0, 0
self.maskVAO, self.maskVBO = 0, 0
self.numOfVerts = 0
self.bounds = dict()
self.totalThickness = 0.
self.currentLayer = 0
self.height = 0
self.stlFilename = stlFilename
self.layerThickness = layerThickness
self.sliceSavePath = sliceSavePath
def initializeGL(self):
self.gl = self.context().versionFunctions()
self.shaderProg = QtGui.QOpenGLShaderProgram()
self.shaderProg.create()
self.shaderProg.addShaderFromSourceFile(
QtGui.QOpenGLShader.Vertex, 'shaders/slice.vert')
self.shaderProg.addShaderFromSourceFile(
QtGui.QOpenGLShader.Fragment, 'shaders/slice.frag')
self.shaderProg.link()
self.loadMesh()
self.proj = QtGui.QMatrix4x4()
self.proj.setToIdentity()
self.proj.ortho(0, printer.width*printer.pixel,
0, printer.height*printer.pixel,
-self.totalThickness, self.totalThickness)
self.model = QtGui.QMatrix4x4()
self.model.setToIdentity()
self.model.translate(0, 0, self.totalThickness+EPSILON)
self.sliceFbo = QtGui.QOpenGLFramebufferObject(
printer.width,
printer.height
)
self.sliceFbo.setAttachment(
QtGui.QOpenGLFramebufferObject.CombinedDepthStencil
)
def loadMesh(self):
# Get information about our mesh
ourMesh = mesh.Mesh.from_file(self.stlFilename)
self.numOfVerts = ourMesh.vectors.shape[0] * 3
self.bounds = {
'xmin': np.min(ourMesh.vectors[:,:,0]),
'xmax': np.max(ourMesh.vectors[:,:,0]),
'ymin': np.min(ourMesh.vectors[:,:,1]),
'ymax': np.max(ourMesh.vectors[:,:,1]),
'zmin': np.min(ourMesh.vectors[:,:,2]),
'zmax': np.max(ourMesh.vectors[:,:,2])
}
self.totalThickness = self.bounds['zmax'] - self.bounds['zmin']
#######################################
# make VAO for drawing our mesh
self.vertVAO = QtGui.QOpenGLVertexArrayObject()
self.vertVAO.create()
self.vertVAO.bind()
self.vertVBO = QtGui.QOpenGLBuffer(QtGui.QOpenGLBuffer.VertexBuffer)
self.vertVBO.create()
self.vertVBO.bind()
self.vertVBO.setUsagePattern(QtGui.QOpenGLBuffer.StaticDraw)
data = ourMesh.vectors.astype(GLfloat).tostring()
self.vertVBO.allocate(data, len(data))
self.gl.glVertexAttribPointer(0, 3, self.gl.GL_FLOAT,
self.gl.GL_FALSE, 3*sizeof(GLfloat), 0)
self.gl.glEnableVertexAttribArray(0)
self.vertVBO.release()
self.vertVAO.release()
#######################################
# a mask vertex array for stencil buffer to subtract
maskVert = np.array(
[[0, 0, 0],
[printer.width*printer.pixel, 0, 0],
[printer.width*printer.pixel, printer.height*printer.pixel, 0],
[0, 0, 0],
[printer.width*printer.pixel, printer.height*printer.pixel, 0],
[0, printer.height*printer.pixel, 0]], dtype=GLfloat
)
#######################################
# make VAO for drawing mask
self.maskVAO = QtGui.QOpenGLVertexArrayObject()
self.maskVAO.create()
self.maskVAO.bind()
self.maskVBO = QtGui.QOpenGLBuffer(QtGui.QOpenGLBuffer.VertexBuffer)
self.maskVBO.create()
self.maskVBO.bind()
self.maskVBO.setUsagePattern(QtGui.QOpenGLBuffer.StaticDraw)
data = maskVert.tostring()
self.maskVBO.allocate(data, len(data))
self.gl.glVertexAttribPointer(0, 3, self.gl.GL_FLOAT,
self.gl.GL_FALSE, 3*sizeof(GLfloat), 0)
self.gl.glEnableVertexAttribArray(0)
self.maskVBO.release()
self.maskVAO.release()
#######################################
def paintGL(self):
if self.height >= self.totalThickness-EPSILON:
sys.exit()
else:
self.height += self.layerThickness
self.currentLayer += 1
self.draw()
self.renderSlice()
self.update()
def draw(self):
self.gl.glViewport(0, 0, self.size().width(), self.size().height())
self.gl.glEnable(self.gl.GL_STENCIL_TEST)
self.gl.glClearColor(0., 0., 0., 1.)
self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT | self.gl.GL_STENCIL_BUFFER_BIT)
self.vertVAO.bind()
self.shaderProg.bind()
self.model.translate(0, 0, -self.layerThickness)
self.shaderProg.setUniformValue('proj', self.proj)
self.shaderProg.setUniformValue('model', self.model)
self.gl.glEnable(self.gl.GL_CULL_FACE)
self.gl.glCullFace(self.gl.GL_FRONT)
self.gl.glStencilFunc(self.gl.GL_ALWAYS, 0, 0xFF)
self.gl.glStencilOp(self.gl.GL_KEEP, self.gl.GL_KEEP, self.gl.GL_INCR)
self.gl.glDrawArrays(self.gl.GL_TRIANGLES, 0, self.numOfVerts)
self.gl.glCullFace(self.gl.GL_BACK)
self.gl.glStencilOp(self.gl.GL_KEEP, self.gl.GL_KEEP, self.gl.GL_DECR)
self.gl.glDrawArrays(self.gl.GL_TRIANGLES, 0, self.numOfVerts)
self.gl.glDisable(self.gl.GL_CULL_FACE)
self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT)
self.maskVAO.bind()
self.gl.glStencilFunc(self.gl.GL_NOTEQUAL, 0, 0xFF)
self.gl.glStencilOp(self.gl.GL_KEEP, self.gl.GL_KEEP, self.gl.GL_KEEP)
self.gl.glDrawArrays(self.gl.GL_TRIANGLES, 0, 6)
self.gl.glDisable(self.gl.GL_STENCIL_TEST)
self.shaderProg.release()
def renderSlice(self):
self.sliceFbo.bind()
self.gl.glViewport(0, 0, printer.width, printer.height)
self.gl.glEnable(self.gl.GL_STENCIL_TEST)
self.gl.glClearColor(0., 0., 0., 1.)
self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT | self.gl.GL_STENCIL_BUFFER_BIT)
self.vertVAO.bind()
self.shaderProg.bind()
self.shaderProg.setUniformValue('proj', self.proj)
self.shaderProg.setUniformValue('model', self.model)
self.gl.glEnable(self.gl.GL_CULL_FACE)
self.gl.glCullFace(self.gl.GL_FRONT)
self.gl.glStencilFunc(self.gl.GL_ALWAYS, 0, 0xFF)
self.gl.glStencilOp(self.gl.GL_KEEP, self.gl.GL_KEEP, self.gl.GL_INCR)
self.gl.glDrawArrays(self.gl.GL_TRIANGLES, 0, self.numOfVerts)
self.gl.glCullFace(self.gl.GL_BACK)
self.gl.glStencilOp(self.gl.GL_KEEP, self.gl.GL_KEEP, self.gl.GL_DECR)
self.gl.glDrawArrays(self.gl.GL_TRIANGLES, 0, self.numOfVerts)
self.gl.glDisable(self.gl.GL_CULL_FACE)
self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT)
self.maskVAO.bind()
self.gl.glStencilFunc(self.gl.GL_NOTEQUAL, 0, 0xFF)
self.gl.glStencilOp(self.gl.GL_KEEP, self.gl.GL_KEEP, self.gl.GL_KEEP)
self.gl.glDrawArrays(self.gl.GL_TRIANGLES, 0, 6)
self.gl.glDisable(self.gl.GL_STENCIL_TEST)
image = self.sliceFbo.toImage()
# makes a QComboBox for different Image Format,
# namely Format_Mono, Format_MonoLSB, and Format_Grayscale8
image = image.convertToFormat(QtGui.QImage.Format_Grayscale8)
image.save(os.path.join(self.sliceSavePath,
'out{:04d}.png'.format(self.currentLayer)))
self.sliceFbo.release()
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
sys.exit()
event.accept()
def main(stlFilename, layerThickness):
temp = os.path.dirname(stlFilename)
sliceSavePath = os.path.join(temp, 'slices')
# remove old slices folder if there is one, and make a new empty one
if os.path.exists(sliceSavePath):
shutil.rmtree(sliceSavePath) # using shutil here avoids permissions errors
if not os.path.exists(sliceSavePath):
os.mkdir(sliceSavePath)
# Set format here, otherwise it throws error
# `QCocoaGLContext: Falling back to unshared context.`
# on Mac when use QOpenGLWidgets
# https://doc.qt.io/qt-5/qopenglwidget.html#details last paragraph
format = QtGui.QSurfaceFormat()
format.setRenderableType(QtGui.QSurfaceFormat.OpenGL)
format.setProfile(QtGui.QSurfaceFormat.CoreProfile)
format.setVersion(4, 1)
format.setStencilBufferSize(8)
QtGui.QSurfaceFormat.setDefaultFormat(format)
app = QtWidgets.QApplication(sys.argv)
window = Window(stlFilename, layerThickness, sliceSavePath)
window.resize(SCR_WIDTH, SCR_HEIGHT)
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main(sys.argv[1], float(sys.argv[2]))

Related

Trying to make alienrain in python using the opengl function glMapBufferRange

Just 4 little lines causing a problem with the alien rain program that I ported from the OpenGL Superbible. It seems I am having issues trying to write to memory after using the function glMapBufferRange
Update: Excellent code by Rabbid76 has solved the problem and provided valuable insight of explanation. Thank You.
Required files: ktxloader.py , aliens.ktx
Source code of alienrain.py
#!/usr/bin/python3
import sys
import time
sys.path.append("./shared")
#from sbmloader import SBMObject # location of sbm file format loader
from ktxloader import KTXObject # location of ktx file format loader
#from sbmath import m3dDegToRad, m3dRadToDeg, m3dTranslateMatrix44, m3dRotationMatrix44, m3dMultiply, m3dOrtho, m3dPerspective, rotation_matrix, translate, m3dScaleMatrix44
fullscreen = True
import numpy.matlib
import numpy as np
import math
try:
from OpenGL.GLUT import *
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, glBindVertexArray
except:
print ('''
ERROR: PyOpenGL not installed properly.
''')
sys.exit()
identityMatrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
render_prog = GLuint(0)
render_vao = GLuint(0)
tex_alien_array = GLuint(0)
rain_buffer = GLuint(0)
droplet_x_offset = []
droplet_rot_speed = []
droplet_fall_speed = []
seed = 0x13371337
import random
import ctypes
random.seed (0x13371337)
def random_float():
# global seed
# res=0.0
# tmp=0
# seed *= 16807;
# tmp = seed ^ (seed >> 4) ^ (seed << 15);
# res = (tmp >> 9) | 0x3F800000;
# return (res - 1.0);
return (random.random() - 1.0)
class Scene:
def __init__(self, width, height):
global render_prog
global render_vao
global tex_alien_array
global rain_buffer
global droplet_x_offset, droplet_rot_speed, droplet_fall_speed
self.width = width
self.height = height
vs = GLuint(0)
fs = GLuint(0)
vs_source = '''
#version 410 core
layout (location = 0) in int alien_index;
out VS_OUT
{
flat int alien;
vec2 tc;
} vs_out;
struct droplet_t
{
float x_offset;
float y_offset;
float orientation;
float unused;
};
layout (std140) uniform droplets
{
droplet_t droplet[256];
};
void main(void)
{
const vec2[4] position = vec2[4](vec2(-0.5, -0.5),
vec2( 0.5, -0.5),
vec2(-0.5, 0.5),
vec2( 0.5, 0.5));
vs_out.tc = position[gl_VertexID].xy + vec2(0.5);
float co = cos(droplet[alien_index].orientation);
float so = sin(droplet[alien_index].orientation);
mat2 rot = mat2(vec2(co, so),
vec2(-so, co));
vec2 pos = 0.25 * rot * position[gl_VertexID];
gl_Position = vec4(pos.x + droplet[alien_index].x_offset,
pos.y + droplet[alien_index].y_offset,
0.5, 1.0);
vs_out.alien = alien_index % 64;
}
'''
fs_source = '''
#version 410 core
layout (location = 0) out vec4 color;
in VS_OUT
{
flat int alien;
vec2 tc;
} fs_in;
uniform sampler2DArray tex_aliens;
void main(void)
{
color = texture(tex_aliens, vec3(fs_in.tc, float(fs_in.alien)));
}
'''
vs = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vs, vs_source)
glCompileShader(vs)
glGetShaderInfoLog(vs)
fs = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fs, fs_source)
glCompileShader(fs)
glGetShaderInfoLog(vs)
render_prog = glCreateProgram()
glAttachShader(render_prog, vs)
glAttachShader(render_prog, fs)
glLinkProgram(render_prog)
glDeleteShader(vs)
glDeleteShader(fs)
glGetProgramInfoLog(render_prog)
glGenVertexArrays(1, render_vao)
glBindVertexArray(render_vao)
ktxobj = KTXObject()
tex_alien_array = ktxobj.ktx_load("aliens.ktx")
glBindTexture(GL_TEXTURE_2D_ARRAY, tex_alien_array)
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glGenBuffers(1, rain_buffer)
glBindBuffer(GL_UNIFORM_BUFFER, rain_buffer)
glBufferData(GL_UNIFORM_BUFFER, 256*4*4, None, GL_DYNAMIC_DRAW)
for i in range(0, 256):
droplet_x_offset.append(random_float() * 2.0 - 1.0)
droplet_rot_speed.append( (random_float() + 0.5) * (-3.0 if (i & 1) else 3.0) )
droplet_fall_speed.append ( random_float() + 0.2 )
glBindVertexArray(render_vao);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
def display(self):
global rain_buffer
green = [ 0.0, 0.1, 0.0, 0.0 ]
currentTime = time.time()
t = currentTime
glViewport(0, 0, self.width, self.height)
glClearBufferfv(GL_COLOR, 0, green)
glUseProgram(render_prog);
glBindBufferBase(GL_UNIFORM_BUFFER, 0, rain_buffer);
droplet = glMapBufferRange(GL_UNIFORM_BUFFER, 0, 256*4*4, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)
float_array = ((ctypes.c_float * 4) * 256).from_address(droplet)
for i in range(0, 256):
float_array[i][0] = droplet_x_offset[i] + 2
float_array[i][1] = 2.0-math.fmod((t + float(i)) * droplet_fall_speed[i], 4.31 ) * random_float()
float_array[i][2] = droplet_rot_speed[i] * t * random_float() * math.pi
float_array[i][3] = 0.0
glUnmapBuffer(GL_UNIFORM_BUFFER);
for alien_index in range(0, 256):
glVertexAttribI1i(0, alien_index);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glutSwapBuffers()
def reshape(self, width, height):
self.width = width
self.height = height
def keyboard(self, key, x, y ):
global fullscreen
global many_cubes
print ('key:' , key)
if key == b'\x1b': # ESC
sys.exit()
elif key == b'f' or key == b'F': #fullscreen toggle
if (fullscreen == True):
glutReshapeWindow(512, 512)
glutPositionWindow(int((1360/2)-(512/2)), int((768/2)-(512/2)))
fullscreen = False
else:
glutFullScreen()
fullscreen = True
print('done')
def init(self):
pass
def timer(self, blah):
glutPostRedisplay()
glutTimerFunc( int(1/60), self.timer, 0)
time.sleep(1/60.0)
if __name__ == '__main__':
start = time.time()
glutInit()
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)
glutInitWindowSize(512, 512)
w1 = glutCreateWindow('OpenGL SuperBible - Alien Rain')
glutInitWindowPosition(int((1360/2)-(512/2)), int((768/2)-(512/2)))
fullscreen = False
many_cubes = False
#glutFullScreen()
scene = Scene(512,512)
glutReshapeFunc(scene.reshape)
glutDisplayFunc(scene.display)
glutKeyboardFunc(scene.keyboard)
glutIdleFunc(scene.display)
#glutTimerFunc( int(1/60), scene.timer, 0)
scene.init()
glutMainLoop()
Current output is:
Update: very similar to expected output. Except it very fast and the rotations of each alien are not as smooth as the expected output. If anyone wants to tinker with the values to get it, please do. Otherwise this question is answered.
The output should be:
Here's the alienrain.cpp that was used to create alienrain.py
First of all, the 2nd parameter of glBufferData is the buffer data in bytes.
Since the glsl data structure is
struct droplet_t
{
float x_offset;
float y_offset;
float orientation;
float unused;
};
layout (std140) uniform droplets
{
droplet_t droplet[256];
};
the buffer size is 4*4*256, because the size of a float is 4, t he structure has 4 elements of type flaot and the array has 256 elements
glBufferData(GL_UNIFORM_BUFFER, 256*4*4, None, GL_DYNAMIC_DRAW)
The instruction
droplet = glMapBufferRange(GL_UNIFORM_BUFFER, 0, 256*4*4, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)
returns a pointer to a allocated memory region with the proper size. You've to fill this memory with data.
The easiest solution is to use the (in python built-in) library ctypes, which has the handy function .from_address():
This method returns a ctypes type instance using the memory specified by address.
so the instruction
float_array = ((ctypes.c_float * 4) * 256).from_address(droplet)
"wraps" a 2 dimensional array to the memory addressed by droplet, with 256 elements and each element has 4 elements of type float.
The values can be set to the array, by a simple assignment statement. e.g.:
import random
import math
import ctypes
glBindBufferBase(GL_UNIFORM_BUFFER, 0, rain_buffer);
droplet = glMapBufferRange(GL_UNIFORM_BUFFER, 0, 256*4*4, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)
float_array = ((ctypes.c_float * 4) * 256).from_address(droplet)
for i in range(0, 256):
float_array[i][0] = random.random() * 2 -1
float_array[i][1] = random.random() * 2 -1
float_array[i][2] = random.random() * math.pi * 2
float_array[i][3] = 0.0

braidTool_Maya_script_error # NameError: name 'QtGui' is not defined

I found someone else's braid tool online and it seems to be working for other people but it doesn't for me. I can't reach that person for help so I am posting here instead.
There's 2 scripts, one is the actual making of the braid and the other is for loading UI. I keep getting The following error:
# Error: Traceback (most recent call last):
# File "<maya console>", line 2, in <module>
# File "/home/ykim/private/maya/2018/scripts/JUM/scripts/braid.py", line 12, in <module>
# list_form, list_base = load_ui_type(ui_file)
# File "/home/ykim/private/maya/2018/scripts/JUM/core/loadUIFile.py", line 30, in load_ui_type
# form_class = frame['Ui_{0}'.format(form_class)]
# File "<string>", line 1, in <module>
# NameError: name 'QtGui' is not defined
#
The following is the 1st script making the braid
import os
from JUM.core.loadUIFile import get_maya_window, load_ui_type
import random
import maya.cmds as cmds
ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'UI','braid.ui')
list_form, list_base = load_ui_type(ui_file)
class espiral(list_form, list_base):
def __init__(self, parent = get_maya_window()):
self.__radius = 0.0
self.__side = 6
self.__variation = 0
self.__circle = ''
self.__pt_position_A = []
self.__pt_position_B = []
self.__pt_position_C = []
self.__path = ''
self.__quantidade = 0.0
self.clock = True
##########################################################
# #
# UI elements #
# #
##########################################################
self.window_name = 'makeSpiralWin'
if cmds.window(self.window_name, exists = True):
cmds.deleteUI(self.window_name)
super(espiral, self).__init__(parent)
self.setupUi(self)
self.btn_selectPath.clicked.connect(self.getPath)
self.btn_create.setDisabled(True)
self.btn_create.clicked.connect(self.create)
def __makeEight(self):
side = 16
offset = self.spin_offset.value()
eight = cmds.circle(nr=(0, 1, 0), c=(0, 0, 0), degree=3, sections=side)
cmds.select(eight[0])
lattice = cmds.lattice(dv = (3, 2, 3), objectCentered = True )
cmds.setAttr(lattice[0]+'.local', 0)
cmds.select(lattice[1]+'.pt[2][0:1][0]',lattice[1]+'.pt[2][0:1][1]',lattice[1]+'.pt[2][0:1][2]')
cmds.scale(1, 1, -1, pivot = (1.108194, 0 , 0), relative = True)
cmds.select(lattice[1]+'.pt[1][0:1][0]',lattice[1]+'.pt[1][0:1][1]',lattice[1]+'.pt[1][0:1][2]')
cmds.scale(0, 0, 0, pivot = (0, 0 , 0), relative = True)
cmds.select(lattice[1]+'.pt[0][0:1][0]',lattice[1]+'.pt[2][0:1][2]',lattice[1]+'.pt[2][0:1][0]',lattice[1]+'.pt[0][0:1][2]')
cmds.scale(1, 1, 1.455556, pivot = (0, 0 , 0), relative = True)
cmds.scale(0.929167, 1, 1, pivot = (0, 0 , 0), relative = True)
cmds.select(eight[0])
cmds.delete(ch = True)
cmds.rotate(0,offset,0,eight[0])
cmds.makeIdentity(eight[0],apply = True, t = True, r = True, s = True, n = 0, pn = True)
return eight
def __next(self, porcentagem,eight,scale):
#print porcentagem
curva = self.ln_path.text()
position = cmds.pointOnCurve(curva, top=True, pr=porcentagem, position=True)
tangent = cmds.pointOnCurve(curva, top=True, pr=porcentagem, tangent=True)
angle = cmds.angleBetween(er=True, v1=(0.0, 1.0, 0.0), v2=tangent)
cmds.scale((scale * random.uniform((1-self.spin_random.value()), 1.0)),
(scale * random.uniform((1-self.spin_random.value()), 1.0)),
(scale * random.uniform((1-self.spin_random.value()), 1.0)),
eight[0])
cmds.move(position[0],
position[1],
position[2],
eight[0])
cmds.rotate(angle[0],
angle[1],
angle[2],
eight[0])
def __voltas(self):
steps = 16 * float(self.spin_loops.value())
porcent = 1.0 / steps
return int(steps), porcent
def __makeMesh(self,curva):
scale_0 = self.spin_radius.value()
scale_1 = self.spin_radius_1.value()
scale = self.spin_radius.value()
if (scale_0 >= scale_1):
tempMaior = scale_0
tempMenor = scale_1
else:
tempMaior = scale_1
tempMenor = scale_0
scale_extrude = tempMenor/tempMaior
position = cmds.pointOnCurve(curva, top=True, pr=0, position=True)
tangent = cmds.pointOnCurve(curva, top=True, pr=0, normalizedTangent=True)
angle = cmds.angleBetween(er=True, v1=(0.0, 1.0, 0.0), v2=tangent)
circle = cmds.circle(nr=(0, 1, 0), c=(0, 0, 0), degree=3, sections=16, r = 0.5)
cmds.scale(tempMaior,
tempMaior,
tempMaior,
circle[0])
cmds.move(position[0],
position[1],
position[2],
circle[0])
cmds.rotate(angle[0],
angle[1],
angle[2],
circle[0])
extrude = cmds.extrude(circle[0],
curva,
constructionHistory = True,
range = True,
polygon = 0,
extrudeType = 2,
useComponentPivot = 0,
fixedPath = 0,
useProfileNormal = 1,
rotation = 0,
scale = scale_extrude,
reverseSurfaceIfPathReversed = 1)
poly = cmds.nurbsToPoly(extrude[0], matchNormalDir = True, constructionHistory = False, format = 2, polygonType = 1, uType = 3, uNumber = 1, vType = 3, vNumber = 3)
cmds.delete(circle, extrude[0])
print poly
return poly
def __curve(self):
curve_A = cmds.curve(p=self.__pt_position_A)
curve_B = cmds.curve(p=self.__pt_position_B)
curve_C = cmds.curve(p=self.__pt_position_C)
if (self.btn_makeMesh.isChecked()):
mesh_A = self.__makeMesh(curve_A)
mesh_B = self.__makeMesh(curve_B)
mesh_C = self.__makeMesh(curve_C)
cmds.delete(curve_A, curve_B, curve_C)
cmds.select(mesh_A,mesh_B,mesh_C)
else:
cmds.select(curve_A,curve_B,curve_C)
def __braid(self):
steps, porcent = self.__voltas()
increment = porcent
eight = self.__makeEight()
list = range(int(steps))
offset = self.spin_offset.value()
offset_normalize = offset/360.0
self.progress_Create.setRange(0,len(list))
scale_0 = self.spin_radius.value()
scale_1 = self.spin_radius_1.value()
if (scale_0 >= scale_1):
scale_maior = scale_0
scale_menor = scale_1
else:
scale_maior = scale_1
scale_menor = scale_0
diference = scale_maior-scale_menor
percent = diference/steps
scale = scale_maior
if (self.btn_reverse.isChecked()):
curva = self.ln_path.text()
cmds.reverseCurve(curva,ch = False, replaceOriginal = True)
for i in list:
self.progress_Create.setValue(i)
self.__next(porcent,eight,scale)
porcent += increment
_pos_A = (i*0.0625)%1.0 + offset_normalize
_pos_B = (i*0.0625+0.333333)%1.0 + offset_normalize
_pos_C = (i*0.0625+0.666666666)%1.0 + offset_normalize
self.__pt_position_A.append(cmds.pointOnCurve( eight[0],top = True, pr= _pos_A, p=True ))
self.__pt_position_B.append(cmds.pointOnCurve( eight[0],top = True, pr= _pos_B, p=True ))
self.__pt_position_C.append(cmds.pointOnCurve( eight[0],top = True, pr= _pos_C, p=True ))
scale -= percent
self.progress_Create.reset()
#cmds.delete(self.__circle[0])
# return self.__pt_position
self.__curve()
cmds.delete(eight[0])
def getPath(self):
path = cmds.ls(sl = True)
if path == []:
self.ln_path.setText('Nothing selected')
self.btn_create.setDisabled(True)
self.ln_path.setStyleSheet("background-color: rgb(110, 90, 90);")
else:
shape_path = cmds.listRelatives(path[0])
if (cmds.objectType(shape_path)== 'nurbsCurve'):
self.ln_path.setText(path[0])
self.btn_create.setEnabled(True)
self.ln_path.setStyleSheet("background-color: rgb(90, 150, 50);")
else:
self.ln_path.setText('Path not valid')
self.btn_create.setDisabled(True)
self.ln_path.setStyleSheet("background-color: rgb(110, 90, 90);")
def create(self):
self.__braid()
self.__pt_position_A = []
self.__pt_position_B = []
self.__pt_position_C = []
def run():
espira = espiral()
espira.show()
Another script is for loading UI design
import shiboken2
from PySide2 import QtWidgets
from PySide2 import QtGui
import maya.OpenMayaUI as apiUI
from cStringIO import StringIO
import pyside2uic
import xml.etree.ElementTree as xml
def get_maya_window():
ptr = apiUI.MQtUtil.mainWindow()
if ptr is not None:
return shiboken.wrapInstance(long(ptr), QtGui.QMainWindow)
def load_ui_type(ui_file):
parsed = xml.parse(ui_file)
widget_class = parsed.find('widget').get('class')
form_class = parsed.find('class').text
with open(ui_file,'r') as f:
o = StringIO()
frame = {}
pysideu2ic.compileUi(f, o, indent = 0)
pyc = compile(o.getvalue(), '<string>', 'exec')
exec pyc in frame
# Fetch the base_class and form class based on their type in the xml from design
form_class = frame['Ui_{0}'.format(form_class)]
base_class = eval('QtGui.{0}'.format(widget_class))
return form_class, base_class

PyQTGraph ImageExporter bug related to image size

I am using pyqtgraph to produce plots in a PyQT window.
I want to export PNG image of the graphs.
I have this error when I try to export my plots:
ImageExporter.py", line 70, in export
bg = np.empty((self.params['width'], self.params['height'], 4), >dtype=np.ubyte)
TypeError: 'float' object cannot be interpreted as an integer
I noticed that the self.params['width'] and self.params['height'] are floats. But np.empty can not create an erray using float sizes.
Even if i set the width and height manually using :
exporter.parameters()['width'] = self.raw_DataPlot.width()
exporter.parameters()['height'] = self.raw_DataPlot.height()
The results are floats.
I noticed that If I change the line 70 of ImageExporter.py to :
bg = np.empty((int(self.params['width']), int(self.params['height']), 4),dtype=np.ubyte)
The export works fine.
Is it possible to address this issue and update the library?
Or is there a workaround that doesn't push me to change the pyqtgraph library itself.
Thanks
To solve this problem, I created a new ImageExporter that I integrated to my project.
I called this exporter PQG_ImageExporter so that we don't get confused.
I signaled this problem on github. I hope they fix it soon. When the problem will be solved, I can get back to the classic ImageExporter :
from pyqtgraph.exporters import Exporter
from pyqtgraph.parametertree import Parameter
from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
from pyqtgraph import functions as fn
import numpy as np
import pyqtgraph as pg
__all__ = ['PQG_ImageExporter']
class PQG_ImageExporter(Exporter):
Name = "Image File (PNG, TIF, JPG, ...)"
allowCopy = True
def __init__(self, item):
Exporter.__init__(self, item)
tr = self.getTargetRect()
if isinstance(item, QtGui.QGraphicsItem):
scene = item.scene()
else:
scene = item
# scene.views()[0].backgroundBrush()
bgbrush = pg.mkBrush('w')
bg = bgbrush.color()
if bgbrush.style() == QtCore.Qt.NoBrush:
bg.setAlpha(0)
self.params = Parameter(name='params', type='group', children=[
{'name': 'width', 'type': 'int',
'value': tr.width(), 'limits': (0, None)},
{'name': 'height', 'type': 'int',
'value': tr.height(), 'limits': (0, None)},
{'name': 'antialias', 'type': 'bool', 'value': True},
{'name': 'background', 'type': 'color', 'value': bg},
])
self.params.param('width').sigValueChanged.connect(self.widthChanged)
self.params.param('height').sigValueChanged.connect(self.heightChanged)
def widthChanged(self):
sr = self.getSourceRect()
ar = float(sr.height()) / sr.width()
self.params.param('height').setValue(
self.params['width'] * ar, blockSignal=self.heightChanged)
def heightChanged(self):
sr = self.getSourceRect()
ar = float(sr.width()) / sr.height()
self.params.param('width').setValue(
self.params['height'] * ar, blockSignal=self.widthChanged)
def parameters(self):
return self.params
def export(self, fileName=None, toBytes=False, copy=False):
if fileName is None and not toBytes and not copy:
if USE_PYSIDE:
filter = ["*."+str(f)
for f in QtGui.QImageWriter.supportedImageFormats()]
else:
filter = ["*."+bytes(f).decode('utf-8')
for f in QtGui.QImageWriter.supportedImageFormats()]
preferred = ['*.png', '*.tif', '*.jpg']
for p in preferred[::-1]:
if p in filter:
filter.remove(p)
filter.insert(0, p)
self.fileSaveDialog(filter=filter)
return
targetRect = QtCore.QRect(
0, 0, self.params['width'], self.params['height'])
sourceRect = self.getSourceRect()
#self.png = QtGui.QImage(targetRect.size(), QtGui.QImage.Format_ARGB32)
# self.png.fill(pyqtgraph.mkColor(self.params['background']))
w, h = self.params['width'], self.params['height']
if w == 0 or h == 0:
raise Exception(
"Cannot export image with size=0 (requested export size is %dx%d)" % (w, h))
bg = np.empty((int(self.params['width']), int(
self.params['height']), 4), dtype=np.ubyte)
color = self.params['background']
bg[:, :, 0] = color.blue()
bg[:, :, 1] = color.green()
bg[:, :, 2] = color.red()
bg[:, :, 3] = color.alpha()
self.png = fn.makeQImage(bg, alpha=True)
# set resolution of image:
origTargetRect = self.getTargetRect()
resolutionScale = targetRect.width() / origTargetRect.width()
#self.png.setDotsPerMeterX(self.png.dotsPerMeterX() * resolutionScale)
#self.png.setDotsPerMeterY(self.png.dotsPerMeterY() * resolutionScale)
painter = QtGui.QPainter(self.png)
#dtr = painter.deviceTransform()
try:
self.setExportMode(True, {
'antialias': self.params['antialias'], 'background': self.params['background'], 'painter': painter, 'resolutionScale': resolutionScale})
painter.setRenderHint(
QtGui.QPainter.Antialiasing, self.params['antialias'])
self.getScene().render(painter, QtCore.QRectF(
targetRect), QtCore.QRectF(sourceRect))
finally:
self.setExportMode(False)
painter.end()
if copy:
QtGui.QApplication.clipboard().setImage(self.png)
elif toBytes:
return self.png
else:
self.png.save(fileName)
PQG_ImageExporter.register()

python pyqt matplotlib computation GUI app plotting wrong

everyone!
I am working on a project to use python pyqt4 to design a GUI for computing V-I curves of PV panel. I want to input the required parameter and compute the PV panel output current and use matplotlib to plot the V-I curve.
My code work well. However, I found that the curve I get from the app is wrong, while directly compute it is right. The computation methods are exactly same. But when I put the computation in GUI is different.
The code is here:
import sys
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import fsolve
q = 1.6 * (10 ** -19)
k = 1.38 * (10 ** -23)
Tref = 25
Eg = 1.1
def calReverseSaturationCurrent(Isc,Voc,Ns,A,Tc):
a = np.exp(q*Voc/(Ns*k*A*Tc))-1
return Isc/a
def calSaturationCurrent(Irs,Tc,A):
return Irs*((Tc/Tref)**3)*np.exp(q*Eg*(1/Tref - 1/Tc)/(k*A))
def calPhotocurrent(Isc,Ki,Tc,insolation):
return (Isc + Ki*(Tc-Tref))*insolation
def calOutputCurrent(Isc,Voc,Ns,A,Tc,Ki,insolation,Np,Rs,Rsh,V):
Irs = calReverseSaturationCurrent(Isc,Voc,Ns,A,Tc)
Is = calSaturationCurrent(Irs,Tc,A)
Iph = calPhotocurrent(Isc,Ki,Tc,insolation)
I0 = 0
I = np.array([])
def f(ii,*arg):
temp_v = arg[0]
return ii - Np*Iph+Np*Is*(np.exp(q*(temp_v/Ns+ii*Rs/Np)/(k*Tc*A))-1)
for vv in V:
ii=fsolve(f,I0,args=vv)
I = np.append(I,ii)
return I
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
self.toolbar.hide()
self.idealFactor = QtGui.QLabel('Ideal Factor A')
self.shortCircuitCurrent = QtGui.QLabel('Short Circuit Current')
self.openCircuitVoltage = QtGui.QLabel('Open Circuit Voltage')
self.temperatureCoefficient = QtGui.QLabel('Temperature Coefficient')
self.seriesCells = QtGui.QLabel('Series Cells')
self.parallelCells = QtGui.QLabel('Parallel Cells')
self.seriesResistance = QtGui.QLabel('Series Resistance')
self.shuntResistance = QtGui.QLabel('Shunt Resistance')
self.outputVoltageMin = QtGui.QLabel('Min Output Voltage')
self.outputVoltageMax = QtGui.QLabel('Max Output Voltage')
self.workingTemperature = QtGui.QLabel('Working Temperature')
self.isolation = QtGui.QLabel('Isolation')
self.idealFactorEdit = QtGui.QDoubleSpinBox()
self.idealFactorEdit.setValue(1.0)
self.shortCircuitCurrentEdit = QtGui.QDoubleSpinBox()
self.shortCircuitCurrentEdit.setValue(3.17)
self.openCircuitVoltageEdit = QtGui.QDoubleSpinBox()
self.openCircuitVoltageEdit.setValue(21.8)
self.temperatureCoefficientEdit = QtGui.QDoubleSpinBox()
self.temperatureCoefficientEdit.setValue(0.065)
self.seriesCellsEdit = QtGui.QDoubleSpinBox()
self.seriesCellsEdit.setValue(36.0)
self.parallelCellsEdit = QtGui.QDoubleSpinBox()
self.parallelCellsEdit.setValue(1.0)
self.seriesResistanceEdit = QtGui.QDoubleSpinBox()
self.seriesResistanceEdit.setValue(0.1)
self.shuntResistanceEdit = QtGui.QDoubleSpinBox()
self.shuntResistanceEdit.setValue(float("inf"))
self.outputVoltageMinEdit = QtGui.QDoubleSpinBox()
self.outputVoltageMinEdit.setValue(8)
self.outputVoltageMaxEdit = QtGui.QDoubleSpinBox()
self.outputVoltageMaxEdit.setValue(11)
self.workingTemperatureEdit = QtGui.QDoubleSpinBox()
self.workingTemperatureEdit.setValue(25)
self.isolationEdit = QtGui.QDoubleSpinBox()
self.isolationEdit.setValue(1)
self.calBtn = QtGui.QPushButton('Get V-I',self)
self.calBtn.clicked.connect(self.getVI)
grid = QtGui.QGridLayout()
self.setLayout(grid)
grid.addWidget(self.idealFactor, 1, 0)
grid.addWidget(self.idealFactorEdit, 1, 1)
grid.addWidget(self.shortCircuitCurrent, 1, 2)
grid.addWidget(self.shortCircuitCurrentEdit, 1, 3)
grid.addWidget(self.openCircuitVoltage, 2, 0)
grid.addWidget(self.openCircuitVoltageEdit, 2, 1)
grid.addWidget(self.temperatureCoefficient, 2, 2)
grid.addWidget(self.temperatureCoefficientEdit, 2, 3)
grid.addWidget(self.seriesCells, 3, 0)
grid.addWidget(self.seriesCellsEdit, 3, 1)
grid.addWidget(self.parallelCells, 3, 2)
grid.addWidget(self.parallelCellsEdit, 3, 3)
grid.addWidget(self.seriesResistance, 4, 0)
grid.addWidget(self.seriesResistanceEdit, 4, 1)
grid.addWidget(self.shuntResistance, 4, 2)
grid.addWidget(self.shuntResistanceEdit, 4, 3)
grid.addWidget(self.outputVoltageMax, 5, 0)
grid.addWidget(self.outputVoltageMaxEdit, 5, 1)
grid.addWidget(self.outputVoltageMin, 5, 2)
grid.addWidget(self.outputVoltageMinEdit, 5, 3)
grid.addWidget(self.workingTemperature, 6, 0)
grid.addWidget(self.workingTemperatureEdit, 6, 1)
grid.addWidget(self.isolation, 6, 2)
grid.addWidget(self.isolationEdit, 6, 3)
grid.addWidget(self.calBtn, 7,0)
grid.addWidget(self.canvas, 8, 0, 6, 0)
self.setGeometry(300, 300, 1000, 800)
self.setWindowTitle('PYPV')
self.setWindowIcon(QtGui.QIcon('pvPanel.png'))
self.show()
def getVI(self):
A = self.idealFactorEdit.value()
Isc = self.shortCircuitCurrentEdit.value()
Voc = self.openCircuitVoltageEdit.value()
Ki = self.temperatureCoefficientEdit.value()
Ns = self.seriesCellsEdit.value()
Np = self.parallelCellsEdit.value()
Rs = self.seriesResistanceEdit.value()
Rsh = self.shuntResistanceEdit.value()
Vmin = self.outputVoltageMinEdit.value()
Vmax = self.outputVoltageMaxEdit.value()
V = np.linspace(Vmin,Vmax,1000)
Tc = self.workingTemperatureEdit.value()
insolation = self.isolationEdit.value()
I = calOutputCurrent(Isc,Voc,Ns,A,Tc,Ki,insolation,Np,Rs,Rsh,V)
plt.cla()
ax = self.figure.add_subplot(111)
ax.plot(V,I)
self.canvas.draw()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I am sure that the computing method is right. I tested without the GUI. The result is fine. The code is below:
import numpy as np
from scipy.optimize import fsolve
from matplotlib import pyplot as plt
q = 1.6 * (10 ** -19)
k = 1.38 * (10 ** -23)
Tref = 25
Eg = 1.1
A = 1.0
V = np.linspace(8,11,1000)
Isc = 3.17
Ki = 0.07
Ns = 36.0
Np = 1.0
Voc = 21.8
Rs = 0.1
#Rsh = float("inf")
Tc = 25
insolation = 1
def calReverseSaturationCurrent(Isc,Voc,Ns,A,Tc):
a = np.exp(q*Voc/(Ns*k*A*Tc))-1
return Isc/a
def calSaturationCurrent(Irs,Tc,A):
return Irs*((Tc/Tref)**3)*np.exp(q*Eg*(1/Tref - 1/Tc)/(k*A))
def calPhotocurrent(Isc,Ki,Tc,insolation):
return (Isc + Ki*(Tc-Tref))*insolation
def calOutputCurrent(Isc,Voc,Ns,A,Tc,Ki,insolation,Np,Rs,Rsh,V):
Irs = calReverseSaturationCurrent(Isc,Voc,Ns,A,Tc)
Is = calSaturationCurrent(Irs,Tc,A)
Iph = calPhotocurrent(Isc,Ki,Tc,insolation)
I0 = 0
I = np.array([])
def f(ii,*arg):
temp_v = arg[0]
return ii - Np*Iph+Np*Is*(np.exp(q*(temp_v/Ns+ii*Rs/Np)/(k*Tc*A))-1)
for vv in V:
ii=fsolve(f,I0,args=vv)
I = np.append(I,ii)
return I
def makePlots(I,V):
plt.plot(V,I)
plt.show()
def test():
I = calOutputCurrent(Isc,Voc,Ns,A,Tc,Ki,insolation,Np,Rs,Rsh,V)
makePlots(I,V)
if __name__ == "__main__":
test()
Below are the two plots:
Without using GUI
With GUI
English is not my first language. I hope I describe the problem well.
Thanks in advance!
My guess is that you use Python-2, where the / operator behaves differently for integers than for floats. In the first case it is an integer-division: the result is an integer and the remainder is discarded. In the second case it is what you would typically expect from division. This is a source of many bugs so in Python-3 the / operator is always a regular division (returning a float), and the integer-division can be done with //. For more details see PEP-238. Note that your calSaturationCurrent function contains such an integer division: 1/Tc yields 0 in Python-2.
To make Python-2 behave like Python 3 you can use a 'future-import'. If you add the following line at the top of your program (before your other import statements) you will get the same results with and without GUI.
from __future__ import division
Now, you probably also used Python-2 for your non-GUI version, so why did you get different results in the first place? My best guess is that in the gui-version of your program, the values you get from Qt are slightly different (far behind the decimal point that is). The fact that your program is susceptible for this indicates it may have bugs.
For instance, if you print the result of the calSaturationCurrent function, it is 3.42723507299e-122 without GUI, and 0.0 with the GUI. This means that the function f, whose roots you are trying to solve, is always very close to 0 or exactly 0. I would not trust the results, and would start by making a plot of f itself.

How to use winapi SetWinEventHook in python?

I want to get the handle of every new Dialog which pops up from a specific application.
I understand I should set a hook with SetWinEventHook which is in user32.dll in windows, but I don't know how to do that in python. Would you give me an example ?
Here's a very simple example that prints to the console the window text for each dialog that is opened:
import sys
import time
import ctypes
import ctypes.wintypes
EVENT_SYSTEM_DIALOGSTART = 0x0010
WINEVENT_OUTOFCONTEXT = 0x0000
user32 = ctypes.windll.user32
ole32 = ctypes.windll.ole32
ole32.CoInitialize(0)
WinEventProcType = ctypes.WINFUNCTYPE(
None,
ctypes.wintypes.HANDLE,
ctypes.wintypes.DWORD,
ctypes.wintypes.HWND,
ctypes.wintypes.LONG,
ctypes.wintypes.LONG,
ctypes.wintypes.DWORD,
ctypes.wintypes.DWORD
)
def callback(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime):
length = user32.GetWindowTextLengthA(hwnd)
buff = ctypes.create_string_buffer(length + 1)
user32.GetWindowTextA(hwnd, buff, length + 1)
print buff.value
WinEventProc = WinEventProcType(callback)
user32.SetWinEventHook.restype = ctypes.wintypes.HANDLE
hook = user32.SetWinEventHook(
EVENT_SYSTEM_DIALOGSTART,
EVENT_SYSTEM_DIALOGSTART,
0,
WinEventProc,
0,
0,
WINEVENT_OUTOFCONTEXT
)
if hook == 0:
print 'SetWinEventHook failed'
sys.exit(1)
msg = ctypes.wintypes.MSG()
while user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) != 0:
user32.TranslateMessageW(msg)
user32.DispatchMessageW(msg)
user32.UnhookWinEvent(hook)
ole32.CoUninitialize()

Categories