The code bellow try to embed visvis figure in a wx application to make a movie with 3D data. The problem is that everytime I rum the code, I get the error
"./src/unix/glx11.cpp(86): assert "xid" failed in SetCurrent(): window
must be shown" right after calling Figure = app.GetFigureClass()
self.fig = Figure(self)
I spent some time researching this error, but none of the answers suited me. Does anyone knows how to fix it?
import wx
import h5py
import numpy as np
import visvis as vv
app = vv.use('wx')
class CPlot3D (wx.Frame) :
"""
Class for plotting 3D Dirac
"""
def data_for_plotting (self, frame_number) :
"""
Load the data to be plotted for the frame with the frame_number
"""
frame = str(self.frame_names[frame_number])
return self.f[frame][...]
def draw_curent_frame (self, event=None) :
"""
Display the current frame
"""
# Load data
data = self.data_for_plotting (self.frame_number.GetValue())
try :
self.volume_plot.SetData (data)
except AttributeError :
vv.clf()
self.volume_plot = vv.volshow (data, clim=(self.global_min, self.global_max), renderStyle='mip', cm=vv.CM_JET)
a = vv.gca()
vv.ColormapEditor(a)
def GetTicks (axis_num, min_val, max_val, label_format="%.2f") :
size = data.shape[axis_num]
# Number of ticks
nticks = int(np.ceil(np.log2(size)))
nticks += 1 - nticks % 2 # Make <nticks> odd
ticks_position = np.linspace(0, size-1, nticks)
ticks_label = map( lambda x : label_format % x, np.linspace(min_val, max_val, nticks) )
return dict( zip(ticks_position, ticks_label) )
a.axis.xTicks = GetTicks(0, self.x_min, self.x_max)
a.axis.xLabel = "x (rel. units)"
a.axis.yTicks = GetTicks(1, self.y_min, self.y_max)
a.axis.yLabel = "y (rel. units)"
a.axis.zTicks = GetTicks(2, self.z_min, self.z_max)
a.axis.zLabel = "z (rel. units)"
self.fig.Draw()
def __init__ (self, parent, file_name, title="Plot Dirac 3D") :
# Open files
self.f = h5py.File (file_name, 'r')
# Extract the dimension
self.x_gridDIM = int(self.f['x_gridDIM'][...])
self.y_gridDIM = int(self.f['y_gridDIM'][...])
self.z_gridDIM = int(self.f['z_gridDIM'][...])
self.dx = self.f['dx'][...]
self.x_min = self.f['x_min'][...]
self.x_max = self.x_min + self.x_gridDIM * self.dx
self.y_min = self.f['y_min'][...]
self.y_max = self.y_min + self.y_gridDIM * self.dx
self.z_min = self.f['z_min'][...]
self.z_max = self.z_min + self.z_gridDIM * self.dx
# Collect the frame names
self.frame_names = []
for key in self.f.keys () :
try : self.frame_names.append (int(key))
except ValueError: pass
self.frame_names.sort ()
print "\nGet global maximum and minimum..."
# Find the min and max values in all the frames
for frame_number in range(len(self.frame_names)) :
data = self.data_for_plotting (frame_number)
try :
self.global_min = min( self.global_min, data.min() )
self.global_max = max( self.global_max, data.max() )
except AttributeError :
self.global_min = data.min()
self.global_max = data.max()
print "\nStart animation..."
# Create GUI
dw, dh = wx.DisplaySize()
wx.Frame.__init__ (self, parent, title=title, size=(0.4*dw, 0.6*dh) )
self.ConstructGUI ()
self.Center()
self.Show ()
wx.EVT_CLOSE(self, self.on_close)
self.On_animation_button ()
def on_close (self, event) :
try : self.animation_timer.Stop()
except AttributeError : pass
self.Destroy()
def ConstructGUI (self) :
"""
Make a GUI
"""
######################### Navigation bar ##############################
panel = wx.Panel(self)
boxsizer = wx.BoxSizer (wx.HORIZONTAL)
# Frame number indicator
boxsizer.Add (wx.StaticText(panel, label="Frame Number:"))
self.frame_number = wx.SpinCtrl (panel, value="0", min=0, max=len(self.frame_names)-1)
self.frame_number.Bind (wx.EVT_SPINCTRL, self.draw_curent_frame )
boxsizer.Add (self.frame_number)
# Go to the beginning button
self.go_beginnign_button = wx.Button (panel, label="<<")
self.Bind (wx.EVT_BUTTON, self.go_to_beginning, self.go_beginnign_button)
boxsizer.Add (self.go_beginnign_button)
# Animation button
self.animation_button_start_label = "Play animation "
self.animation_button_stop_label = "STOP animation"
self.animation_button = wx.Button (panel, label=self.animation_button_start_label)
self.Bind (wx.EVT_BUTTON, self.On_animation_button, self.animation_button)
boxsizer.Add (self.animation_button)
# Go to the end button
self.go_end_button = wx.Button (panel, label=">>")
self.Bind (wx.EVT_BUTTON, self.go_to_end, self.go_end_button)
boxsizer.Add (self.go_end_button)
panel.SetSizer (boxsizer)
############################# Setting up visvis binding #######################################
Figure = app.GetFigureClass()
self.fig = Figure(self)
################################### Layout #####################################################
sizer = wx.BoxSizer (wx.VERTICAL)
sizer.Add (panel, flag=wx.CENTER)
sizer.Add(self.fig._widget, 1, flag=wx.EXPAND)
self.SetSizer(sizer)
self.SetAutoLayout(True)
self.Layout()
def On_animation_button (self, event=None) :
"""
<self.animation_button> was clicked
"""
if self.animation_button.GetLabel() == self.animation_button_start_label :
# Begin playing animation
# Set up timer for animation
timer_id = wx.NewId ()
self.animation_timer = wx.Timer (self, timer_id)
self.animation_timer.Start (200)
def on_animation_timer (event) :
self.draw_curent_frame()
position = self.frame_number.GetValue()
if position > len(self.frame_names)-2 : self.On_animation_button ()
else : self.frame_number.SetValue (position+1)
wx.EVT_TIMER (self, timer_id, on_animation_timer)
# Channing the button's label
self.animation_button.SetLabel(self.animation_button_stop_label)
else : # Stop playing animation
self.animation_timer.Stop ()
del self.animation_timer
# Channing the button's label
self.animation_button.SetLabel(self.animation_button_start_label)
def go_to_beginning (self, event) :
"""
<self.go_beginnign_button> was clicked
"""
self.frame_number.SetValue (0)
self.draw_curent_frame()
def go_to_end (self, event) :
"""
<self.go_end_button> was clicked
"""
self.frame_number.SetValue (len(self.frame_names)-1)
self.draw_curent_frame()
if __name__ == '__main__' :
import sys
app.Create()
# Loading files
if len(sys.argv) <> 2 :
openFileDialog = wx.FileDialog(None, "Open HDF5 file to load 3D Dirac", "", "",
"HDF5 files (*.hdf5)|*.hdf5", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_CHANGE_DIR)
# Check whether user canceled
if openFileDialog.ShowModal() == wx.ID_CANCEL:
print "Error: file name is needed as argument"
exit()
else : filename = openFileDialog.GetPath()
else : filename = sys.argv[1]
CPlot3D (None, filename)
app.Run ()
When you call On_animation_button() manually, your frame is not actually shown yet, even though you had called Show() on it because, at least with X11, showing happens asynchronously. So you need to delay calling it until later. This can be done by e.g. binding a lambda doing this to EVT_SIZE event (because by the time you get it, the window is already initialized) or just by using CallAfter().
Related
I have a problem, but this time it has more relationship with Wxpython than with Tkinter. I don't usually use this module, so I know very little about it.
In the following code, pressing the Enter key in the Tkinter test window will open the windows printing window. If sent to print to PDF, it works perfect.
But if the process is repeated, an error will occur. Below are the test code and the error.
CODE
from threading import Thread
from tkinter import Tk
import wx
def f_imprimir(codigo):
class TextDocPrintout(wx.Printout):
def __init__(self):
wx.Printout.__init__(self)
def OnPrintPage(self, page):
dc = self.GetDC()
ppiPrinterX, ppiPrinterY = self.GetPPIPrinter()
ppiScreenX, ppiScreenY = self.GetPPIScreen()
logScale = float(ppiPrinterX)/float(ppiScreenX)
pw, ph = self.GetPageSizePixels()
dw, dh = dc.GetSize()
scale = logScale * float(dw)/float(pw)
dc.SetUserScale(scale, scale)
logUnitsMM = float(ppiPrinterX)/(logScale*25.4)
codigo(dc, logUnitsMM)
return True
class PrintFrameworkSample(wx.Frame):
def OnPrint(self):
pdata = wx.PrintData()
pdata.SetPaperId(wx.PAPER_A4)
pdata.SetOrientation(wx.LANDSCAPE)
data = wx.PrintDialogData(pdata)
printer = wx.Printer(data)
printout = TextDocPrintout()
useSetupDialog = True
if not printer.Print(self, printout, useSetupDialog) and printer.GetLastError() == wx.PRINTER_ERROR:
wx.MessageBox(
"There was a problem printing.\n"
"Perhaps your current printer is not set correctly?",
"Printing Error", wx.OK)
else:
data = printer.GetPrintDialogData()
printout.Destroy()
self.Destroy()
app=wx.App(False)
PrintFrameworkSample().OnPrint()
def funcion(dc, MM):
dc.DrawText("hola mundo", MM*16, MM*73)
def imprimir(codigo):
t = Thread(target=f_imprimir, args=(codigo,))
t.start()
Tk().bind("<Return>", lambda Event:imprimir(funcion))
ERROR
File "C:\Users\DANTE\Google Drive\JNAAB\DESARROLLO\pruebas\t\s\prueba.py", line 11, in OnPrintPage
dc = self.GetDC()
AttributeError: 'TextDocPrintout' object has no attribute 'GetDC'
Does anyone know the solution to the problem? Thank you.
I did the tests and this code should be a solution for all the problems I have, including this one. I suppose this works because it gives Tkinter time to update the window before the Printer Selection Window starts.
from tkinter import Tk, Entry
import wx
def f_imprimir(v,codigo):
class TextDocPrintout(wx.Printout):
"""
A printout class that is able to print simple text documents.
Does not handle page numbers or titles, and it assumes that no
lines are longer than what will fit within the page width. Those
features are left as an exercise for the reader. ;-)
"""
def __init__(self):#, text, title, margins):
wx.Printout.__init__(self)#, title)
self.numPages = 1
def HasPage(self, page):
return page <= self.numPages
def GetPageInfo(self):
return (1, self.numPages, 1, self.numPages)
def CalculateScale(self, dc):
# Scale the DC such that the printout is roughly the same as
# the screen scaling.
ppiPrinterX, ppiPrinterY = self.GetPPIPrinter()
ppiScreenX, ppiScreenY = self.GetPPIScreen()
logScale = float(ppiPrinterX)/float(ppiScreenX)
# Now adjust if the real page size is reduced (such as when
# drawing on a scaled wx.MemoryDC in the Print Preview.) If
# page width == DC width then nothing changes, otherwise we
# scale down for the DC.
pw, ph = self.GetPageSizePixels()
dw, dh = dc.GetSize()
scale = logScale * float(dw)/float(pw)
# Set the DC's scale.
dc.SetUserScale(scale, scale)
# Find the logical units per millimeter (for calculating the
# margins)
self.logUnitsMM = float(ppiPrinterX)/(logScale*25.4)
def OnPreparePrinting(self):
# calculate the number of pages
dc = self.GetDC()
self.CalculateScale(dc)
def OnPrintPage(self, page):
codigo(self.GetDC(), self.logUnitsMM)
return True
class PrintFrameworkSample(wx.Frame):
def __init__(self):
wx.Frame.__init__(self)
# initialize the print data and set some default values
self.pdata = wx.PrintData()
self.pdata.SetPaperId(wx.PAPER_A4)
self.pdata.SetOrientation(wx.PORTRAIT)
def OnPrint(self):#, evt):
data = wx.PrintDialogData(self.pdata)
printer = wx.Printer(data)
printout = TextDocPrintout()
useSetupDialog = True
if not printer.Print(self, printout, useSetupDialog) and printer.GetLastError() == wx.PRINTER_ERROR:
wx.MessageBox(
"Hubo un problema al imprimir.\n"
"Su impresora está configurada correctamente?",
"Error al Imprimir", wx.OK)
else:
data = printer.GetPrintDialogData()
self.pdata = wx.PrintData(data.GetPrintData()) # force a copy
printout.Destroy()
app=wx.App(False)
PrintFrameworkSample().OnPrint()
app.MainLoop()
entrada["state"] = "normal"
def funcion(dc, MM):
dc.DrawText("hola mundo", MM*16, MM*73)
v=Tk()
entrada=Entry(v)
entrada.pack()
after = lambda:v.after(10,lambda:f_imprimir(v,funcion))
v.bind("<Return>", lambda Event:(entrada.config(state="disable"),after()))
I have a piece of code which generates noise. The noise is generated inside a 0.0 to 1.0 range. As long as I set a defined number the code works. If I were to allow users to select maximum range for the number with a slider it stops working.
I have a slider which replaces the value of 1.0 inside the brightness calculation. As soon as I replace 1.0 value inside Brightness with a slider generated value called noiseAttribute the code breaks. It gives no error and technically runs but just makes the object black instead of locking the color value.
import maya.cmds as cmds
import random
import functools
colorList = cmds.ls('colorSet*')
def createUI( pWindowTitle, pNoiseVerts):
windowID = 'myWindowID'
if cmds.window( windowID, exists=True ):
cmds.deleteUI(windowID )
cmds.window( windowID, title=pWindowTitle, s=False, rtf=True )
cmds.rowColumnLayout( numberOfColumns=1, columnWidth=[(1,200)])
cmds.text(label= 'Max Value Lock')
noiseAttribute = cmds.floatSliderGrp( min=0.0, max=1.0, value=1, field=True)
cmds.button( label='Noise', command=functools.partial (addNoise) )
def cancelCallback( *pArgs ):
if cmds.window( windowID, exists=True ):
cmds.deleteUI( windowID)
cmds.button( label='Cancel', command=cancelCallback )
cmds.showWindow()
def pNoiseVerts(object, noiseAttribute):
verts = range(cmds.polyEvaluate(object, vertex=True))
random.shuffle(verts)
for vertex in verts:
cmds.select(object + '.vtx[' + str(vertex) + ']')
brightness = random.uniform(0.0, noiseAttribute)
cmds.polyColorPerVertex(rgb=(brightness, brightness, brightness))
cmds.setAttr(object + '.displayColors', True)
def addNoise(noiseAttribute, *args):
if len(colorList) > 0:
cmds.delete(colorList)
objects = cmds.ls( sl=True, long=True)
if len(objects) > 0:
setList = cmds.ls('colorSet*')
result = cmds.polyColorSet ( create=True, colorSet='colorSet#')
result = cmds.polyColorPerVertex ( rgb=[0.5,0.5,0.5])
result = cmds.polyColorSet ( create=True, colorSet='colorSet#')
for object in objects:
pNoiseVerts(object, noiseAttribute)
else:
cmds.inViewMessage (amg='Message: <hl>Please select an object first</hl>.', pos='midCenter', fade=True )
createUI( 'Config', pNoiseVerts)
As mentioned before the object turns black instead of having its max color value locked.
You dont pass any arguments with your functools
Here is one of my answer on the same topic : Need Help Making Buttons to perform for loops when you input a number
How to print the value of intField in Maya python
Maya Python - Using data from UI
You can go in my history of questions, I answered a lot about functools
import maya.cmds as cmds
import random
import functools
colorList = cmds.ls('colorSet*')
def createUI(pWindowTitle):
windowID = 'myWindowID'
if cmds.window( windowID, exists=True ):
cmds.deleteUI(windowID )
cmds.window( windowID, title=pWindowTitle, s=False, rtf=True )
cmds.rowColumnLayout( numberOfColumns=1, columnWidth=[(1,200)])
cmds.text(label= 'Max Value Lock')
noiseAttribute = cmds.floatSliderGrp( min=0.0, max=1.0, value=1, field=True)
cmds.button( label='Noise', command=functools.partial(ui_addNoise, noiseAttribute) )
def cancelCallback( *pArgs ):
if cmds.window( windowID, exists=True ):
cmds.deleteUI( windowID)
cmds.button( label='Cancel', command=cancelCallback )
cmds.showWindow()
def ui_addNoise(noiseSlider, *args):
value = cmds.floatSliderGrp(noiseSlider, q=True, value=True)
addNoise(value)
def pNoiseVerts(object, value):
verts = range(cmds.polyEvaluate(object, vertex=True))
random.shuffle(verts)
for id in verts:
# you should never select things in maya, pass it as variable :
vtx = '{}.vtx[{}]'.format(object, id)
brightness = random.uniform(0.0, value)
cmds.polyColorPerVertex(vtx, rgb=(brightness, brightness, brightness))
cmds.setAttr(object + '.displayColors', True)
def addNoise(value):
if len(colorList) > 0:
cmds.delete(colorList)
objects = cmds.ls( sl=True, long=True)
if len(objects) > 0:
setList = cmds.ls('colorSet*')
result = cmds.polyColorSet ( create=True, colorSet='colorSet#')
result = cmds.polyColorPerVertex ( rgb=[0.5,0.5,0.5])
result = cmds.polyColorSet ( create=True, colorSet='colorSet#')
for object in objects:
pNoiseVerts(object, value)
else:
cmds.inViewMessage (amg='Message: <hl>Please select an object first</hl>.', pos='midCenter', fade=True )
createUI( 'Config')
I need someone's expertise on this exporting problem of mine.
How it works: Select a camera (animated or not is optional) >> File >> Export Selection >> File Type : .chan (need to load this script as a plugin)
Here's where the problem starts. It is able to create a .text file, however, it is not 'exporting' or writing out the contents into the text file and the file size is of zero bytes.
I am making use of the current API that it has been coded, modifying the code to add in some maya cmds
Can someone kindly help me out?
import math, sys, string, os
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
import maya.OpenMayaAnim as OpenMayaAnim
import maya.cmds as cmds
import maya.mel as mel
kPluginTranslatorTypeName = "chan Export/Import"
kVersionNumber = "0.5a"
camSel = []
win_name = "chan_window"
class CustomNodeTranslator(OpenMayaMPx.MPxFileTranslator):
def __init__(self):
OpenMayaMPx.MPxFileTranslator.__init__(self)
def haveWriteMethod(self):
return True
def haveReadMethod(self):
return True
def filter(self):
return " .chan"
def defaultExtension(self):
return "chan"
def writer( self, fileObject, optionString, accessMode ):
try:
fullName = fileObject.fullName()
fileHandle = open(fullName,"w")
selectList = OpenMaya.MSelectionList()
OpenMaya.MGlobal.getActiveSelectionList(selectList)
node = OpenMaya.MObject()
depFn = OpenMaya.MFnDependencyNode()
path = OpenMaya.MDagPath()
iterator = OpenMaya.MItSelectionList(selectList)
animationTime = OpenMayaAnim.MAnimControl()
maxTime = int(animationTime.maxTime().value())
minTime = int(animationTime.minTime().value())
while (iterator.isDone() == 0):
iterator.getDependNode(node)
depFn.setObject(node)
iterator.getDagPath(path, node)
cameraObject = OpenMaya.MFnCamera(path)
transform = OpenMaya.MFnTransform(path)
chanMe = fileExporter(transform, minTime, maxTime, cameraObject)
for all in chanMe():
fileHandle.write(all)
iterator.next()
fileHandle.close()
except:
sys.stderr.write( "Failed to write file information\n")
raise
def processLine( self, lineStr ):
self.importTheChan.writeFrameData(lineStr)
class fileExporter():
""" module for exporting chan files from application. arguments: object, startFrame, endFrame """
def __init__(self, transform, startAnimation, endAnimation, cameraObj):
self.fileExport = []
self.transform = transform
self.cameraObj = cameraObj
self.start = startAnimation
self.end = endAnimation
self.exportWin()
def exportWin(self):
self.expWindow = cmds.window(w=150, h=100, title = "Export Selection" )
cmds.columnLayout( adjustableColumn=True )
form = cmds.formLayout(numberOfDivisions=100)
cmds.radioCollection()
self.chk1 = cmds.radioButton( label='option1', onc = self.opt1On, ofc = self.opt1Off )
self.chk2 = cmds.radioButton( label='option2', onc = self.opt2On, ofc = self.opt2Off )
self.okayBtn = cmds.button(label='okay!', command=self.runSel, width=150, height=35)
cmds.formLayout(form, edit=True, attachForm=[\
(self.chk1, 'top', 15),\
(self.chk1, 'left', 15),\
(self.chk2, 'top', 30),\
(self.chk2, 'left', 15),\
(self.okayBtn, 'top', 50),\
(self.okayBtn, 'left', 15)])
cmds.showWindow( self.expWindow )
def opt1On(self, args):
print "User checked option1"
startAnimation = cmds.playbackOptions(query=True, minTime=True)
endAnimation = cmds.playbackOptions(query=True, maxTime=True)
self.start = startAnimation
self.end = endAnimation
def opt1Off(self, args):
print "User un-checked option1"
cmds.radioButton(self.chk2, edit = True, enable = True)
self.start = ""
self.end = ""
def opt2On(self, args):
print "User checked option2"
startAnimation = cmds.findKeyframe(which='first')
endAnimation = cmds.findKeyframe(which='last')
self.start = startAnimation
self.end = endAnimation
#self.start.append(int(startAnimation))
#self.end.append(int(endAnimation))
def opt2Off(self, args):
print "User un-checked option2"
self.start = ""
self.end = ""
def runSel(self, args):
chkVal1 = cmds.radioButton(self.chk1, query=True, sl=1)
chkVal2 = cmds.radioButton(self.chk2, query=True, sl=1)
if chkVal1 == 1:
print "opt1 Pressed!"
print self.start
print self.end
self.test()
self.closeWindow()
elif chkVal2 == 1:
print "opt2 Pressed!"
print self.start
print self.end
self.test()
self.closeWindow()
else:
cmds.warning("Check an option")
def closeWindow(self):
cmds.deleteUI(self.expWindow, window=True)
def test(self):
self.actualExp(self.transform, self.start, self.end, self.cameraObj)
def actualExp(self, transform, startAnimation, endAnimation, cameraObj):
mayaGlobal = OpenMaya.MGlobal()
mayaGlobal.viewFrame(OpenMaya.MTime(1))
# Converts the float arguement into integer
for i in range(int(startAnimation), int(endAnimation + 1)):
focalLength = cameraObj.focalLength()
vFilmApp = cameraObj.verticalFilmAperture()
focalOut = 2 math.degrees(math.atan(vFilmApp 25.4/ (2 focalLength)))
myEuler = OpenMaya.MEulerRotation()
spc = OpenMaya.MSpace.kWorld
trans = transform.getTranslation(spc)
rotation = transform.getRotation(myEuler)
rotVector = OpenMaya.MVector(myEuler.asVector())
self.fileExport.append((str(i) + '\t' + str(trans[0]) + "\t" + str(trans[1]) + "\t" + str(trans[2]) + "\t" + str(math.degrees(rotVector[0])) + "\t" + str(math.degrees(rotVector[1])) + "\t" + str(math.degrees(rotVector[2])) + "\t" + str(focalOut) + "\n"))
mayaGlobal.viewFrame(OpenMaya.MTime(i+1))
def __call__(self, args):
return self.fileExport
def radianToDegree(self, radians):
outDegrees = 0.0
outDegrees = (float(radians) / (math.pi)) 180
return outDegrees
# creator
def translatorCreator():
return OpenMayaMPx.asMPxPtr( CustomNodeTranslator() )
# initialize the script plug-in
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.registerFileTranslator(kPluginTranslatorTypeName, None, translatorCreator)
except:
sys.stderr.write( "Failed to register translator: %s" % kPluginTranslatorTypeName )
raise
# uninitialize the script plug-in
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.deregisterFileTranslator( kPluginTranslatorTypeName )
except:
sys.stderr.write( "Failed to deregister translator: %s" % kPluginTranslatorTypeName )
raise
the __call__ method is what's supposed to provide the contents of the file. It returns self.fileExport, which is an empty list that is not getting populated.
The problem here is the writer method of the plugin will not wait for your exportWin UI to return the user inputs when you call
chanMe = fileExporter(transform, minTime, maxTime, cameraObject)
By the time the user has entered the inputs, the statements that follow have already been executed:
for all in chanMe():
fileHandle.write(all)
iterator.next()
fileHandle.close()
That is why plugin-based file exporters like these have their options UI tucked away in the option box. These options will be passed prior to call to the plugin's writer().
You will need to export your options UI code (in a certain specific format) using MEL in another script file and specify the name of that file in the optionsScriptName param of the registerFileTranslator call. There is a communication protocol that needs to be followed for communication between this options UI and the writer plugin itself. RobTheBloke's awesome post illustrates this process.
Ideally, the writer() method should have all the details it needs for computing and exporting without having to wait for user input.
Alternatively, if you prefer to have your own UI window and more control over the flow of things, you could write the exporter not as a plugin, but as a simple MEL/Python module. You could still use the power of the API.
Hope this helped!
Using the minimal example below, the line plot of a large (some 110k points) plot I get (with python 2.7, numpy 1.5.1, chaco/enable/traits 4.3.0) is this:
However, that is bizarre, because it is a line plot, and there shouldn't be any filled areas in there? Especially since the data is sawtooth-ish signal? It's as if there is a line at y~=37XX, above which there is color filling?! But sure enough, if I zoom into an area, I get the rendering I expect - without the unexpected fill:
Is this a bug - or is there something I'm doing wrong? I tried to use use_downsampling, but it makes no difference...
The test code:
import numpy as np
import numpy.random as npr
from pprint import pprint
from traits.api import HasTraits, Instance
from chaco.api import Plot, ArrayPlotData, VPlotContainer
from traitsui.api import View, Item
from enable.component_editor import ComponentEditor
from chaco.tools.api import PanTool, BetterSelectingZoom
tlen = 112607
alr = npr.randint(0, 4000, tlen)
tx = np.arange(0.0, 30.0-0.00001, 30.0/tlen)
ty = np.arange(0, tlen, 1) % 10000 + alr
pprint(len(ty))
class ChacoTest(HasTraits):
container = Instance(VPlotContainer)
traits_view = View(
Item('container', editor=ComponentEditor(), show_label=False),
width=800, height=500, resizable=True,
title="Chaco Test"
)
def __init__(self):
super(ChacoTest, self).__init__()
pprint(ty)
self.plotdata = ArrayPlotData(x = tx, y = ty)
self.plotobj = Plot(self.plotdata)
self.plotA = self.plotobj.plot(("x", "y"), type="line", color=(0,0.99,0), spacing=0, padding=0, alpha=0.7, use_downsampling=True)
self.container = VPlotContainer(self.plotobj, spacing=5, padding=5, bgcolor="lightgray")
#~ container.add(plot)
self.plotobj.tools.append(PanTool(self.plotobj))
self.plotobj.overlays.append(BetterSelectingZoom(self.plotobj))
if __name__ == "__main__":
ChacoTest().configure_traits()
I am able to reproduce the error and talking with John Wiggins (maintainer of Enable), it is a bug in kiva (which chaco uses to paint on the screen):
https://github.com/enthought/enable
The good news is that this is a bug in one of the kiva backend that you can use. So to go around the issue, you can run your script choosing a different backend:
ETS_TOOLKIT=qt4.qpainter python <NAME OF YOUR SCRIPT>
if you use qpainter or quartz, the plot looks (on my machine) as expected. If you choose qt4.image (the Agg backend), you will reproduce the issue. Unfortunately, the Agg backend is the default one. To change that, you can set the ETS_TOOLKIT environment variable to that value:
export ETS_TOOLKIT=qt4.qpainter
The bad news is that fixing this isn't going to be an easy task. Please feel free to report the bug in github (again https://github.com/enthought/enable) if you want to be involved in this. If you don't, I will log it in the next couple of days. Thanks for reporting it!
Just a note - I found this:
[Enthought-Dev] is chaco faster than matplotlib
I recall reading somewhere that you are expected to implement the
_downsample method because the optimal algorithm depends on the type
of data you're collecting.
And as I couldn't find any examples with _downsample implementation other than decimated_plot.py referred in that post, which isn't standalone - I tried and built a standalone example, included below.
The example basically has messed up drag and zoom, (plot disappears if you go out of range, or stretches upon a drag move) - and it starts zoomed in; but it is possible to zoom it out in the range shown in the OP - and then it displays the exact same plot rendering problem. So downsampling isn't the solution per se, so this is likely a bug?
import numpy as np
import numpy.random as npr
from pprint import pprint
from traits.api import HasTraits, Instance
from chaco.api import Plot, ArrayPlotData, VPlotContainer
from traitsui.api import View, Item
from enable.component_editor import ComponentEditor
from chaco.tools.api import PanTool, BetterSelectingZoom
#
from chaco.api import BaseXYPlot, LinearMapper, AbstractPlotData
from enable.api import black_color_trait, LineStyle
from traits.api import Float, Enum, Int, Str, Trait, Event, Property, Array, cached_property, Bool, Dict
from chaco.abstract_mapper import AbstractMapper
from chaco.abstract_data_source import AbstractDataSource
from chaco.array_data_source import ArrayDataSource
from chaco.data_range_1d import DataRange1D
tlen = 112607
alr = npr.randint(0, 4000, tlen)
tx = np.arange(0.0, 30.0-0.00001, 30.0/tlen)
ty = np.arange(0, tlen, 1) % 10000 + alr
pprint(len(ty))
class ChacoTest(HasTraits):
container = Instance(VPlotContainer)
traits_view = View(
Item('container', editor=ComponentEditor(), show_label=False),
width=800, height=500, resizable=True,
title="Chaco Test"
)
downsampling_cutoff = Int(4)
def __init__(self):
super(ChacoTest, self).__init__()
pprint(ty)
self.plotdata = ArrayPlotData(x = tx, y = ty)
self.plotobj = TimeSeriesPlot(self.plotdata)
self.plotobj.setplotranges("x", "y")
self.container = VPlotContainer(self.plotobj, spacing=5, padding=5, bgcolor="lightgray")
self.plotobj.tools.append(PanTool(self.plotobj))
self.plotobj.overlays.append(BetterSelectingZoom(self.plotobj))
# decimate from:
# https://bitbucket.org/mjrosen/neurobehavior/raw/097ef3719d1263a8b303d29c31ab71b6e792ab04/cns/widgets/views/decimated_plot.py
def decimate(data, screen_width, downsampling_cutoff=4, mode='extremes'):
data_width = data.shape[-1]
downsample = np.floor((data_width/screen_width)/4.)
if downsample > downsampling_cutoff:
return globals()['decimate_'+mode](data, downsample)
else:
return data
def decimate_extremes(data, downsample):
last_dim = data.ndim
offset = data.shape[-1] % downsample
if data.ndim == 2:
shape = (len(data), -1, downsample)
else:
shape = (-1, downsample)
data = data[..., offset:].reshape(shape).copy()
data_min = data.min(last_dim)
data_max = data.max(last_dim)
return data_min, data_max
def decimate_mean(data, downsample):
offset = len(data) % downsample
if data.ndim == 2:
shape = (-1, downsample, data.shape[-1])
else:
shape = (-1, downsample)
data = data[offset:].reshape(shape).copy()
return data.mean(1)
# based on class from decimated_plot.py, also
# neurobehavior/cns/chaco_exts/timeseries_plot.py ;
# + some other code from chaco
class TimeSeriesPlot(BaseXYPlot):
color = black_color_trait
line_width = Float(1.0)
line_style = LineStyle
reference = Enum('most_recent', 'trigger')
traits_view = View("color#", "line_width")
downsampling_cutoff = Int(100)
signal_trait = "updated"
decimate_mode = Str('extremes')
ch_index = Trait(None, Int, None)
# Mapping of data names from self.data to their respective datasources.
datasources = Dict(Str, Instance(AbstractDataSource))
index_mapper = Instance(AbstractMapper)
value_mapper = Instance(AbstractMapper)
def __init__(self, data=None, **kwargs):
super(TimeSeriesPlot, self).__init__(**kwargs)
self._index_mapper_changed(None, self.index_mapper)
self.setplotdata(data)
self._plot_ui_info = None
return
def setplotdata(self, data):
if data is not None:
if isinstance(data, AbstractPlotData):
self.data = data
elif type(data) in (ndarray, tuple, list):
self.data = ArrayPlotData(data)
else:
raise ValueError, "Don't know how to create PlotData for data" \
"of type " + str(type(data))
def setplotranges(self, index_name, value_name):
self.index_name = index_name
self.value_name = value_name
index = self._get_or_create_datasource(index_name)
value = self._get_or_create_datasource(value_name)
if not(self.index_mapper):
imap = LinearMapper()#(range=self.index_range)
self.index_mapper = imap
if not(self.value_mapper):
vmap = LinearMapper()#(range=self.value_range)
self.value_mapper = vmap
if not(self.index_range): self.index_range = DataRange1D() # calls index_mapper
if not(self.value_range): self.value_range = DataRange1D()
self.index_range.add(index) # calls index_mapper!
self.value_range.add(value)
# now do it (right?):
self.index_mapper = LinearMapper(range=self.index_range)
self.value_mapper = LinearMapper(range=self.value_range)
def _get_or_create_datasource(self, name):
if name not in self.datasources:
data = self.data.get_data(name)
if type(data) in (list, tuple):
data = array(data)
if isinstance(data, np.ndarray):
if len(data.shape) == 1:
ds = ArrayDataSource(data, sort_order="none")
elif len(data.shape) == 2:
ds = ImageData(data=data, value_depth=1)
elif len(data.shape) == 3:
if data.shape[2] in (3,4):
ds = ImageData(data=data, value_depth=int(data.shape[2]))
else:
raise ValueError("Unhandled array shape in creating new plot: " \
+ str(data.shape))
elif isinstance(data, AbstractDataSource):
ds = data
else:
raise ValueError("Couldn't create datasource for data of type " + \
str(type(data)))
self.datasources[name] = ds
return self.datasources[name]
def get_screen_points(self):
self._gather_points()
return self._downsample()
def _data_changed(self):
self.invalidate_draw()
self._cache_valid = False
self._screen_cache_valid = False
self.request_redraw()
def _gather_points(self):
if not self._cache_valid:
range = self.index_mapper.range
#if self.reference == 'most_recent':
# values, t_lb, t_ub = self.get_recent_range(range.low, range.high)
#else:
# values, t_lb, t_ub = self.get_range(range.low, range.high, -1)
values, t_lb, t_ub = self.data[self.value_name][range.low:range.high], range.low, range.high
#if self.ch_index is None:
# self._cached_data = values
#else:
# #self._cached_data = values[:,self.ch_index]
self._cached_data = values
self._cached_data_bounds = t_lb, t_ub
self._cache_valid = True
self._screen_cache_valid = False
def _downsample(self):
if not self._screen_cache_valid:
val_pts = self._cached_data
screen_min, screen_max = self.index_mapper.screen_bounds
screen_width = screen_max-screen_min
values = decimate(val_pts, screen_width, self.downsampling_cutoff,
self.decimate_mode)
if type(values) == type(()):
n = len(values[0])
s_val_min = self.value_mapper.map_screen(values[0])
s_val_max = self.value_mapper.map_screen(values[1])
self._cached_screen_data = s_val_min, s_val_max
else:
s_val_pts = self.value_mapper.map_screen(values)
self._cached_screen_data = s_val_pts
n = len(values)
t = np.linspace(*self._cached_data_bounds, num=n)
t_screen = self.index_mapper.map_screen(t)
self._cached_screen_index = t_screen
self._screen_cache_valid = True
return [self._cached_screen_index, self._cached_screen_data]
def _render(self, gc, points):
idx, val = points
if len(idx) == 0:
return
gc.save_state()
gc.set_antialias(True)
gc.clip_to_rect(self.x, self.y, self.width, self.height)
gc.set_stroke_color(self.color_)
gc.set_line_width(self.line_width)
#gc.set_line_width(5)
gc.begin_path()
#if len(val) == 2:
if type(val) == type(()):
starts = np.column_stack((idx, val[0]))
ends = np.column_stack((idx, val[1]))
gc.line_set(starts, ends)
else:
gc.lines(np.column_stack((idx, val)))
gc.stroke_path()
self._draw_default_axes(gc)
gc.restore_state()
if __name__ == "__main__":
ChacoTest().configure_traits()
I've just started programming in Python and have therefore had an attempt at a simple GUI that consists of a meter (rev counter) and a bar graph. This all works (the code is attached below). However I'm getting very bad flicker due to the screen refreshing (Windows XP). I know I need to use a Buffered DC however I can't work out from all the posts what I actually need to do.
However my assumptions are:
Initially create a Memory DC for the Buffered image to 'reside' in and then
Instantiate the Buffered DC
Bind the On Paint to the Buffered DC.
To provide the relevant meter face (scale) I've drawn it all in Autocad and have then converted it to a jpg (HMIV0.2.bmp) upon which the bar graph and meter needle are superimposed.
As you'll note I'm using the Refresh at the end of my loop and I'm wondering if this is 'bad practice'. However my intention is for the program to free run and 'pick up' the relevant values (revs) on each pass of the loop. Thereby having no external Event to trigger the paint.
Any advise or pointer in the right direction are highly appreciated...I bought Cody Precord's 'Wx.Python 2.8 Application Development Cookbook' in the hope of some inspiration but alas.
import wx
import random
import time
import math
def Data():
data_value =random.randint(0,400)
return data_value
def Pointer():
meter_value =float(random.randint(0,260))
Needle_centre_x = 253
Needle_centre_y = 239
Needle_length = float(125)
Needle_sweep = math.radians(214) #Number of degrees (converted to radians) that the needle is to deflect through
Meter_max_scale = 260 # maximum value on the meter scale
lo_reflect = float(20) # Meter reading that are less than this value are below the horizontal-Lo
Angle_per_digit = Needle_sweep / Meter_max_scale # Angle_per_digit is in radians
print '*******************NEW CYCLE*****************************'
print 'The meter value is ' +str(meter_value)
Start_displac = Angle_per_digit * lo_reflect
needle_ang = -1*(Start_displac -(meter_value * Angle_per_digit))
Needle_x = Needle_length * (math.cos(needle_ang))
Needle_y = Needle_length * (math.sin(needle_ang))
needle_degrees = math.degrees(needle_ang)
anglea = needle_ang - math.pi/2
angleb = needle_ang + math.pi/2
base_x = 10*(math.cos(anglea))
base_y = 10*(math.sin(anglea))
print 'The needle angle is' + str(needle_degrees)
print 'Angle A is ' + str(math.degrees(anglea))
print 'Angle B is ' + str(math.degrees(angleb))
print 'The needle deflection angle is ' + str(math.degrees(needle_ang))
basea_y = int(Needle_centre_y- base_y)
basea_x = int(Needle_centre_x - base_x)
baseb_y = int(Needle_centre_y + base_y)
baseb_x = int(Needle_centre_x + base_x)
needle_y = int(Needle_centre_y - Needle_y)
needle_x = int(Needle_centre_x - Needle_x)
Needle = [Needle_centre_x,Needle_centre_y,needle_x,needle_y,basea_x,basea_y,baseb_x,baseb_y] #Needle = [x1,y1,x2,y2,pointa_y,pointa_x,pointb_y,pointb_x]
return Needle
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent,pos = (0,0), size = (800,500))
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, data_value):
data_value = Data()
dc = wx.PaintDC(self)
bmp = wx.Bitmap("HMIV0.2.bmp")
dc.DrawBitmap(bmp, 0, 0)
if data_value > 300:
dc.SetBrush(wx.RED_BRUSH)
dc.DrawRoundedRectangle(12,450,40,-1*(data_value),2)
dc.SetBrush(wx.BLUE_BRUSH)
dc.DrawRoundedRectangle(12,450,40,-1*300,2)
dc.SetBrush(wx.GREEN_BRUSH)
dc.DrawRoundedRectangle(12,450,40,-1*200,2)
if data_value < 300 and data_value > 200 :
dc.SetBrush(wx.BLUE_BRUSH)
dc.DrawRoundedRectangle(12,450,40,-1*(data_value),2)
dc.SetBrush(wx.GREEN_BRUSH)
dc.DrawRoundedRectangle(12,450,40,-1*200,2)
if data_value < 200:
dc.SetBrush(wx.GREEN_BRUSH)
dc.DrawRoundedRectangle(12,450,40,-1*(data_value),2)
dc.SetBrush(wx.BLUE_BRUSH)
HMI_needle = Pointer()
print 'the contents of HMI needle are' + str(HMI_needle)
print 'Needle_centre_x,Needle_centre_y,needle_x,needle_y,basea_x,basea_y,baseb_x,baseb_y'
print type(HMI_needle)
Points = [(HMI_needle[2],HMI_needle[3]),(HMI_needle[4],HMI_needle[5]),(HMI_needle[6],HMI_needle[7])]
dc.DrawPolygon(Points)
dc.DrawCircle(253,239,20)
time.sleep(0.1)
self.Refresh()
class MyFrame(wx.Frame):
def __init__(self, parent, title, size):
wx.Frame.__init__(self, parent, wx.ID_ANY, title, wx.DefaultPosition, size,)
bitmap = wx.Bitmap("Background.jpg", type=wx.BITMAP_TYPE_JPEG)
self.bitmap =wx.StaticBitmap(self,bitmap =bitmap)
self.Panel = MyPanel(self)
app = wx.PySimpleApp(redirect=False, filename = "C:\output.txt", clearSigInt=True)
frame = MyFrame(None,"WxPaint", size=(800,500))#size=(800,480))
frame.Show()
app.SetTopWindow(frame)
app.MainLoop()
Use wx.BG_STYLE_CUSTOM on your wx.Panel
Use wx.AutoBufferedPaintDC in your OnPaint handler
See my example code here:
Best Canvas for WxPython
It is rarely good practice to put a delay in an event handler, mostly because it prevents other events from being handled. Instead of ending your OnPaint handler with:
time.sleep(0.1)
self.Refresh()
you should use:
wx.CallLater(100,self.Refresh)
In addition, I have found to avoid flicker, I disable background erase with:
self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: 0)