class MapFrameBase(wx.Frame):
"""!Base class for map display window
Derived class must use statusbarManager or override
GetProperty, SetProperty and HasProperty methods.
If derived class enables and disables auto-rendering,
it should override IsAutoRendered method.
"""
def __init__(self, parent = None, id = wx.ID_ANY, title = None,
style = wx.DEFAULT_FRAME_STYLE, toolbars = None,
Map = None, auimgr = None, name = None, **kwargs):
"""!
#param toolbars array of activated toolbars, e.g. ['map', 'digit']
#param Map instance of render.Map
#param auimgs AUI manager
#param name frame name
#param kwargs wx.Frame attributes
"""
self.Map = Map # instance of render.Map
self.parent = parent
wx.Frame.__init__(self, parent, id, title, style = style, name = name, **kwargs)
# available cursors
self.cursors = {
# default: cross
# "default" : wx.StockCursor(wx.CURSOR_DEFAULT),
"default" : wx.StockCursor(wx.CURSOR_ARROW),
"cross" : wx.StockCursor(wx.CURSOR_CROSS),
"hand" : wx.StockCursor(wx.CURSOR_HAND),
"pencil" : wx.StockCursor(wx.CURSOR_PENCIL),
"sizenwse": wx.StockCursor(wx.CURSOR_SIZENWSE)
}
#
# set the size & system icon
#
self.SetClientSize(self.GetSize())
self.iconsize = (16, 16)
self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_map.ico'), wx.BITMAP_TYPE_ICO))
# toolbars
self.toolbars = {}
#
# Fancy gui
#
self._mgr = wx.aui.AuiManager(self)
def _initMap(self, map):
"""!Initialize map display, set dimensions and map region
"""
if not grass.find_program('g.region', ['--help']):
sys.exit(_("GRASS module '%s' not found. Unable to start map "
"display window.") % 'g.region')
self.width, self.height = self.GetClientSize()
Debug.msg(2, "MapFrame._initMap():")
map.ChangeMapSize(self.GetClientSize())
map.region = map.GetRegion() # g.region -upgc
# self.Map.SetRegion() # adjust region to match display window
def SetProperty(self, name, value):
"""!Sets property"""
self.statusbarManager.SetProperty(name, value)
def GetProperty(self, name):
"""!Returns property"""
return self.statusbarManager.GetProperty(name)
def HasProperty(self, name):
"""!Checks whether object has property"""
return self.statusbarManager.HasProperty(name)
def GetPPM(self):
"""! Get pixel per meter
#todo now computed every time, is it necessary?
#todo enable user to specify ppm (and store it in UserSettings)
"""
# TODO: need to be fixed...
### screen X region problem
### user should specify ppm
dc = wx.ScreenDC()
dpSizePx = wx.DisplaySize() # display size in pixels
dpSizeMM = wx.DisplaySizeMM() # display size in mm (system)
dpSizeIn = (dpSizeMM[0] / 25.4, dpSizeMM[1] / 25.4) # inches
sysPpi = dc.GetPPI()
comPpi = (dpSizePx[0] / dpSizeIn[0],
dpSizePx[1] / dpSizeIn[1])
ppi = comPpi # pixel per inch
ppm = ((ppi[0] / 2.54) * 100, # pixel per meter
(ppi[1] / 2.54) * 100)
Debug.msg(4, "MapFrameBase.GetPPM(): size: px=%d,%d mm=%f,%f "
"in=%f,%f ppi: sys=%d,%d com=%d,%d; ppm=%f,%f" % \
(dpSizePx[0], dpSizePx[1], dpSizeMM[0], dpSizeMM[1],
dpSizeIn[0], dpSizeIn[1],
sysPpi[0], sysPpi[1], comPpi[0], comPpi[1],
ppm[0], ppm[1]))
return ppm
def SetMapScale(self, value, map = None):
"""! Set current map scale
#param value scale value (n if scale is 1:n)
#param map Map instance (if none self.Map is used)
"""
if not map:
map = self.Map
region = self.Map.region
dEW = value * (region['cols'] / self.GetPPM()[0])
dNS = value * (region['rows'] / self.GetPPM()[1])
region['n'] = region['center_northing'] + dNS / 2.
region['s'] = region['center_northing'] - dNS / 2.
region['w'] = region['center_easting'] - dEW / 2.
region['e'] = region['center_easting'] + dEW / 2.
# add to zoom history
self.GetWindow().ZoomHistory(region['n'], region['s'],
region['e'], region['w'])
def GetMapScale(self, map = None):
"""! Get current map scale
#param map Map instance (if none self.Map is used)
"""
if not map:
map = self.Map
region = map.region
ppm = self.GetPPM()
heightCm = region['rows'] / ppm[1] * 100
widthCm = region['cols'] / ppm[0] * 100
Debug.msg(4, "MapFrame.GetMapScale(): width_cm=%f, height_cm=%f" %
(widthCm, heightCm))
xscale = (region['e'] - region['w']) / (region['cols'] / ppm[0])
yscale = (region['n'] - region['s']) / (region['rows'] / ppm[1])
scale = (xscale + yscale) / 2.
Debug.msg(3, "MapFrame.GetMapScale(): xscale=%f, yscale=%f -> scale=%f" % \
(xscale, yscale, scale))
return scale
def GetProgressBar(self):
"""!Returns progress bar
Progress bar can be used by other classes.
"""
return self.statusbarManager.GetProgressBar()
def GetMap(self):
"""!Returns current Map instance
"""
return self.Map
def GetWindow(self):
"""!Get map window"""
return self.MapWindow
def GetMapToolbar(self):
"""!Returns toolbar with zooming tools"""
raise NotImplementedError()
def GetToolbar(self, name):
"""!Returns toolbar if exists else None.
Toolbars dictionary contains currently used toolbars only.
"""
if name in self.toolbars:
return self.toolbars[name]
return None
def StatusbarUpdate(self):
"""!Update statusbar content"""
self.statusbarManager.Update()
def IsAutoRendered(self):
"""!Check if auto-rendering is enabled"""
return self.GetProperty('render')
def CoordinatesChanged(self):
"""!Shows current coordinates on statusbar.
Used in BufferedWindow to report change of map coordinates (under mouse cursor).
"""
self.statusbarManager.ShowItem('coordinates')
def StatusbarReposition(self):
"""!Reposition items in statusbar"""
self.statusbarManager.Reposition()
def StatusbarEnableLongHelp(self, enable = True):
"""!Enable/disable toolbars long help"""
for toolbar in self.toolbars.itervalues():
toolbar.EnableLongHelp(enable)
def IsStandalone(self):
"""!Check if Map display is standalone"""
raise NotImplementedError("IsStandalone")
def OnRender(self, event):
"""!Re-render map composition (each map layer)
"""
raise NotImplementedError("OnRender")
def OnDraw(self, event):
"""!Re-display current map composition
"""
self.MapWindow.UpdateMap(render = False)
def OnErase(self, event):
"""!Erase the canvas
"""
self.MapWindow.EraseMap()
def OnZoomIn(self, event):
"""!Zoom in the map.
Set mouse cursor, zoombox attributes, and zoom direction
"""
toolbar = self.GetMapToolbar()
self._switchTool(toolbar, event)
win = self.GetWindow()
self._prepareZoom(mapWindow = win, zoomType = 1)
def OnZoomOut(self, event):
"""!Zoom out the map.
Set mouse cursor, zoombox attributes, and zoom direction
"""
toolbar = self.GetMapToolbar()
self._switchTool(toolbar, event)
win = self.GetWindow()
self._prepareZoom(mapWindow = win, zoomType = -1)
def _prepareZoom(self, mapWindow, zoomType):
"""!Prepares MapWindow for zoom, toggles toolbar
#param mapWindow MapWindow to prepare
#param zoomType 1 for zoom in, -1 for zoom out
"""
mapWindow.mouse['use'] = "zoom"
mapWindow.mouse['box'] = "box"
mapWindow.zoomtype = zoomType
mapWindow.pen = wx.Pen(colour = 'Red', width = 2, style = wx.SHORT_DASH)
# change the cursor
mapWindow.SetCursor(self.cursors["cross"])
def _switchTool(self, toolbar, event):
"""!Helper function to switch tools"""
if toolbar:
toolbar.OnTool(event)
toolbar.action['desc'] = ''
def OnPointer(self, event):
"""!Sets mouse mode to pointer."""
self.MapWindow.mouse['use'] = 'pointer'
def OnPan(self, event):
"""!Panning, set mouse to drag
"""
toolbar = self.GetMapToolbar()
self._switchTool(toolbar, event)
win = self.GetWindow()
self._preparePan(mapWindow = win)
def _preparePan(self, mapWindow):
"""!Prepares MapWindow for pan, toggles toolbar
#param mapWindow MapWindow to prepare
"""
mapWindow.mouse['use'] = "pan"
mapWindow.mouse['box'] = "pan"
mapWindow.zoomtype = 0
# change the cursor
mapWindow.SetCursor(self.cursors["hand"])
def OnZoomBack(self, event):
"""!Zoom last (previously stored position)
"""
self.MapWindow.ZoomBack()
def OnZoomToMap(self, event):
"""!
Set display extents to match selected raster (including NULLs)
or vector map.
"""
self.MapWindow.ZoomToMap(layers = self.Map.GetListOfLayers())
def OnZoomToWind(self, event):
"""!Set display geometry to match computational region
settings (set with g.region)
"""
self.MapWindow.ZoomToWind()
def OnZoomToDefault(self, event):
"""!Set display geometry to match default region settings
"""
self.MapWindow.ZoomToDefault()
When I try to start up GrassRaPlat in command prompt, appears this as follow:
GRASS 6.4.3RC2 (AMC):~ > Traceback (most recent call last):
File "/usr/local/src/grass-6.4.3RC2/dist.i686-pc-linux-gnu/etc/wxpython/lmgr/frame.py", line 196, in init
self.NewDisplay(show = False)
File "/usr/local/src/grass-6.4.3RC2/dist.i686-pc-linux-gnu/etc/wxpython/lmgr/frame.py", line 1463, in NewDisplay
auimgr = self._auimgr, showMapDisplay = show)
File "/usr/local/src/grass-6.4.3RC2/dist.i686-pc-linux-gnu/etc/wxpython/lmgr/layertree.py", line 156, in init
Map = self.Map, auimgr = self.auimgr)
File "/usr/local/src/grass-6.4.3RC2/dist.i686-pc-linux-gnu/etc/wxpython/mapdisp/frame.py", line 123, in init
self.statusbarManager.Update()
File "/usr/local/src/grass-6.4.3RC2/dist.i686-pc-linux-gnu/etc/wxpython/mapdisp/statusbar.py", line 217, in Update
item.Update()
File "/usr/local/src/grass-6.4.3RC2/dist.i686-pc-linux-gnu/etc/wxpython/mapdisp/statusbar.py", line 508, in Update
scale = self.mapFrame.GetMapScale()
File "/usr/local/src/grass-6.4.3RC2/dist.i686-pc-linux-gnu/etc/wxpython/gui_core/mapdisp.py", line 168, in GetMapScale
heightCm = region['rows'] / ppm[1] * 100
KeyError: 'rows'
Can you tell me please where is my mistake?
Thank you in advance.
Related
import numpy as np
## Class Code ##
# Shear Class Here WITHOUT the plot method
class Shear(object):
# This is the default constructor to initialise instance variables
# This creates a 'null' Box when a Box object is created
def __init__(self):
# Set attributes of height, width and depth in metres to None
self.angularShear = 10
# Override __repr__ in the object base class
# This will print out the current Box object
def __repr__(self):
return 'Angular Shear: '+str(self.angularShear)+' Shear Strain: '+str(self.shearStrain())+ 'majorAxis: '+str(self.majorAxis())+ 'minorAxis: '+str(self.minorAxis())
# Instance Methods
# Method to calculate the shearstrain of object
def shearStrain(self):
return np.tan(self.angularShear*np.pi/180.)
# Method to calculate the majoraxis shearstrain
def majorAxis(self):
sigma = self.shearStrain()/2.
return ((sigma**2+1.0)**0.5)+sigma
# Method to calculate the minoraxis shearstrain
def minorAxis(self):
sigma = self.shearStrain()/2.
return ((sigma**2+1.0)**0.5)-sigma
not sure what print statement you are referring to in the title, but a way to print what you are asking for is:
import numpy as np
## Class Code ##
# Shear Class Here WITHOUT the plot method
class Shear(object):
# This is the default constructor to initialise instance variables
# This creates a 'null' Box when a Box object is created
# MADE ANGULAR SHEER A PARAMETER
def __init__(self, angularShear):
# Set attributes of height, width and depth in metres to None
self.angularShear = angularShear
# Override __repr__ in the object base class
# This will print out the current Box object
def __repr__(self):
return 'Angular Shear: '+str(self.angularShear)+' Shear Strain: '+str(self.shearStrain())+ 'majorAxis: '+str(self.majorAxis())+ 'minorAxis: '+str(self.minorAxis())
# Instance Methods
# Method to calculate the shearstrain of object
def shearStrain(self):
return np.tan(self.angularShear*np.pi/180.)
# Method to calculate the majoraxis shearstrain
def majorAxis(self):
sigma = self.shearStrain()/2.
return ((sigma**2+1.0)**0.5)+sigma
# Method to calculate the minoraxis shearstrain
def minorAxis(self):
sigma = self.shearStrain()/2.
return ((sigma**2+1.0)**0.5)-sigma
#STUFF ADDED HERE
def main():
for i in range(0, 81, 10):
print(repr(Shear(i)))
main()
[EDIT reponse to comment]
import numpy as np
## Class Code ##
# Shear Class Here WITHOUT the plot method
class Shear(object):
# This is the default constructor to initialise instance variables
# This creates a 'null' Box when a Box object is created
# MADE ANGULAR SHEER A PARAMETER
# def __init__(self):
# # Set attributes of height, width and depth in metres to None
# Override __repr__ in the object base class
# This will print out the current Box object
def __repr__(self):
return_string = ""
for i in range(0, 81, 10):
return_string +='Angular Shear: ' + str(i) + '; Shear Strain: ' + str(
self.shearStrain(i)) + '; majorAxis: ' + str(self.majorAxis(i)) + '; minorAxis: ' + str(self.minorAxis(i)) + "\n"
return return_string
# Instance Methods
# Method to calculate the shearstrain of object
def shearStrain(self,angularShear):
return np.tan(angularShear * np.pi / 180.)
# Method to calculate the majoraxis shearstrain
def majorAxis(self,angularShear):
sigma = self.shearStrain(angularShear) / 2.
return ((sigma ** 2 + 1.0) ** 0.5) + sigma
# Method to calculate the minoraxis shearstrain
def minorAxis(self, angularShear):
sigma = self.shearStrain(angularShear) / 2.
return ((sigma ** 2 + 1.0) ** 0.5) - sigma
#STUFF ADDED HERE
def main():
print(repr(Shear()))
main()
I'm looking for a way to get a buffer from camera2 API in Kivy, like with camera_android.
I am using the code from the Color blind project :https://github.com/inclement/colour-blind-camera/blob/master/camera2/camera2.py
THANKS.
camera_android
from jnius import autoclass, PythonJavaClass, java_method
from kivy.clock import Clock
from kivy.graphics.texture import Texture
from kivy.graphics import Fbo, Callback, Rectangle
from kivy.core.camera import CameraBase
import threading
Camera = autoclass('android.hardware.Camera')
SurfaceTexture = autoclass('android.graphics.SurfaceTexture')
GL_TEXTURE_EXTERNAL_OES = autoclass(
'android.opengl.GLES11Ext').GL_TEXTURE_EXTERNAL_OES
ImageFormat = autoclass('android.graphics.ImageFormat')
class PreviewCallback(PythonJavaClass):
"""
Interface used to get back the preview frame of the Android Camera
"""
__javainterfaces__ = ('android.hardware.Camera$PreviewCallback', )
def __init__(self, callback):
super(PreviewCallback, self).__init__()
self._callback = callback
#java_method('([BLandroid/hardware/Camera;)V')
def onPreviewFrame(self, data, camera):
self._callback(data, camera)
class CameraAndroid(CameraBase):
"""
Implementation of CameraBase using Android API
"""
_update_ev = None
def __init__(self, **kwargs):
self._android_camera = None
self._preview_cb = PreviewCallback(self._on_preview_frame)
self._buflock = threading.Lock()
super(CameraAndroid, self).__init__(**kwargs)
def __del__(self):
self._release_camera()
def init_camera(self):
self._release_camera()
self._android_camera = Camera.open(self._index)
params = self._android_camera.getParameters()
width, height = self._resolution
params.setPreviewSize(width, height)
params.setFocusMode('continuous-picture')
self._android_camera.setParameters(params)
# self._android_camera.setDisplayOrientation()
self.fps = 30.
pf = params.getPreviewFormat()
assert(pf == ImageFormat.NV21) # default format is NV21
self._bufsize = int(ImageFormat.getBitsPerPixel(pf) / 8. *
width * height)
self._camera_texture = Texture(width=width, height=height,
target=GL_TEXTURE_EXTERNAL_OES,
colorfmt='rgba')
self._surface_texture = SurfaceTexture(int(self._camera_texture.id))
self._android_camera.setPreviewTexture(self._surface_texture)
self._fbo = Fbo(size=self._resolution)
self._fbo['resolution'] = (float(width), float(height))
self._fbo.shader.fs = '''
#extension GL_OES_EGL_image_external : require
#ifdef GL_ES
precision highp float;
#endif
/* Outputs from the vertex shader */
varying vec4 frag_color;
varying vec2 tex_coord0;
/* uniform texture samplers */
uniform sampler2D texture0;
uniform samplerExternalOES texture1;
uniform vec2 resolution;
void main()
{
vec2 coord = vec2(tex_coord0.y * (
resolution.y / resolution.x), 1. -tex_coord0.x);
gl_FragColor = texture2D(texture1, tex_coord0);
}
'''
with self._fbo:
self._texture_cb = Callback(lambda instr:
self._camera_texture.bind)
Rectangle(size=self._resolution)
def _release_camera(self):
if self._android_camera is None:
return
self.stop()
self._android_camera.release()
self._android_camera = None
# clear texture and it'll be reset in `_update` pointing to new FBO
self._texture = None
del self._fbo, self._surface_texture, self._camera_texture
def _on_preview_frame(self, data, camera):
with self._buflock:
if self._buffer is not None:
# add buffer back for reuse
self._android_camera.addCallbackBuffer(self._buffer)
self._buffer = data
# check if frame grabbing works
# print self._buffer, len(self.frame_data)
def _refresh_fbo(self):
self._texture_cb.ask_update()
self._fbo.draw()
def start(self):
super(CameraAndroid, self).start()
with self._buflock:
self._buffer = None
for k in range(2): # double buffer
buf = b'\x00' * self._bufsize
self._android_camera.addCallbackBuffer(buf)
self._android_camera.setPreviewCallbackWithBuffer(self._preview_cb)
self._android_camera.startPreview()
if self._update_ev is not None:
self._update_ev.cancel()
self._update_ev = Clock.schedule_interval(self._update, 1 / self.fps)
def stop(self):
super(CameraAndroid, self).stop()
if self._update_ev is not None:
self._update_ev.cancel()
self._update_ev = None
self._android_camera.stopPreview()
self._android_camera.setPreviewCallbackWithBuffer(None)
# buffer queue cleared as well, to be recreated on next start
with self._buflock:
self._buffer = None
def _update(self, dt):
self._surface_texture.updateTexImage()
self._refresh_fbo()
if self._texture is None:
self._texture = self._fbo.texture
self.dispatch('on_load')
self._copy_to_gpu()
def _copy_to_gpu(self):
"""
A dummy placeholder (the image is already in GPU) to be consistent
with other providers.
"""
self.dispatch('on_texture')
def grab_frame(self):
"""
Grab current frame (thread-safe, minimal overhead)
"""
with self._buflock:
if self._buffer is None:
return None
buf = self._buffer.tostring()
return buf
def decode_frame(self, buf):
"""
Decode image data from grabbed frame.
This method depends on OpenCV and NumPy - however it is only used for
fetching the current frame as a NumPy array, and not required when
this :class:`CameraAndroid` provider is simply used by a
:class:`~kivy.uix.camera.Camera` widget.
"""
import numpy as np
from cv2 import cvtColor
w, h = self._resolution
arr = np.fromstring(buf, 'uint8').reshape((h + h / 2, w))
arr = cvtColor(arr, 93) # NV21 -> BGR
return arr
def read_frame(self):
"""
Grab and decode frame in one call
"""
return self.decode_frame(self.grab_frame())
#staticmethod
def get_camera_count():
"""
Get the number of available cameras.
"""
return Camera.getNumberOfCameras()
color blind camera
from kivy.event import EventDispatcher
from kivy.graphics.texture import Texture
from kivy.graphics import Fbo, Callback, Rectangle
from kivy.properties import (BooleanProperty, StringProperty, ObjectProperty, OptionProperty, ListProperty)
from kivy.clock import Clock
from jnius import autoclass, cast, PythonJavaClass, java_method, JavaClass, MetaJavaClass, JavaMethod
import logging
from enum import Enum
logger = logging.getLogger(__file__)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
logger.addHandler(handler)
CameraManager = autoclass("android.hardware.camera2.CameraManager")
PythonActivity = autoclass("org.kivy.android.PythonActivity")
Context = autoclass("android.content.Context")
context = cast("android.content.Context", PythonActivity.mActivity)
CameraDevice = autoclass("android.hardware.camera2.CameraDevice")
CaptureRequest = autoclass("android.hardware.camera2.CaptureRequest")
CameraCharacteristics = autoclass("android.hardware.camera2.CameraCharacteristics")
ArrayList = autoclass('java.util.ArrayList')
JavaArray = autoclass('java.lang.reflect.Array')
SurfaceTexture = autoclass('android.graphics.SurfaceTexture')
Surface = autoclass('android.view.Surface')
GL_TEXTURE_EXTERNAL_OES = autoclass(
'android.opengl.GLES11Ext').GL_TEXTURE_EXTERNAL_OES
ImageFormat = autoclass('android.graphics.ImageFormat')
Handler = autoclass("android.os.Handler")
Looper = autoclass("android.os.Looper")
MyStateCallback = autoclass("net.inclem.camera2.MyStateCallback")
CameraActions = autoclass("net.inclem.camera2.MyStateCallback$CameraActions")
# MyStateCallback = autoclass("org.kivy.android.MyStateCallback")
MyCaptureSessionCallback = autoclass("net.inclem.camera2.MyCaptureSessionCallback")
CameraCaptureEvents = autoclass("net.inclem.camera2.MyCaptureSessionCallback$CameraCaptureEvents")
_global_handler = Handler(Looper.getMainLooper())
class LensFacing(Enum):
"""Values copied from CameraCharacteristics api doc, as pyjnius
lookup doesn't work on some devices.
"""
LENS_FACING_FRONT = 0
LENS_FACING_BACK = 1
LENS_FACING_EXTERNAL = 2
class ControlAfMode(Enum):
CONTROL_AF_MODE_CONTINUOUS_PICTURE = 4
class ControlAeMode(Enum):
CONTROL_AE_MODE_ON = 1
class Runnable(PythonJavaClass):
__javainterfaces__ = ['java/lang/Runnable']
def __init__(self, func):
super(Runnable, self).__init__()
self.func = func
#java_method('()V')
def run(self):
try:
self.func()
except:
import traceback
traceback.print_exc()
class PyCameraInterface(EventDispatcher):
"""
Provides an API for querying details of the cameras available on Android.
"""
camera_ids = []
cameras = ListProperty()
java_camera_characteristics = {}
java_camera_manager = ObjectProperty()
def __init__(self):
super().__init__()
logger.info("Starting camera interface init")
self.java_camera_manager = cast("android.hardware.camera2.CameraManager",
context.getSystemService(Context.CAMERA_SERVICE))
self.camera_ids = self.java_camera_manager.getCameraIdList()
characteristics_dict = self.java_camera_characteristics
camera_manager = self.java_camera_manager
logger.info("Got basic java objects")
for camera_id in self.camera_ids:
logger.info(f"Getting data for camera {camera_id}")
characteristics_dict[camera_id] = camera_manager.getCameraCharacteristics(camera_id)
logger.info("Got characteristics dict")
self.cameras.append(PyCameraDevice(
camera_id=camera_id,
java_camera_manager=camera_manager,
java_camera_characteristics=characteristics_dict[camera_id],
))
logger.info(f"Finished interpreting camera {camera_id}")
def select_cameras(self, **conditions):
options = self.cameras
outputs = []
for camera in cameras:
for key, value in conditions.items():
if getattr(camera, key) != value:
break
else:
outputs.append(camera)
return outputs
class PyCameraDevice(EventDispatcher):
camera_id = StringProperty()
output_texture = ObjectProperty(None, allownone=True)
preview_active = BooleanProperty(False)
preview_texture = ObjectProperty(None, allownone=True)
preview_resolution = ListProperty()
preview_fbo = ObjectProperty(None, allownone=True)
java_preview_surface_texture = ObjectProperty(None)
java_preview_surface = ObjectProperty(None)
java_capture_request = ObjectProperty(None)
java_surface_list = ObjectProperty(None)
java_capture_session = ObjectProperty(None)
connected = BooleanProperty(False)
supported_resolutions = ListProperty()
# TODO: populate this
facing = OptionProperty("UNKNOWN", options=["UNKNOWN", "FRONT", "BACK", "EXTERNAL"])
java_camera_characteristics = ObjectProperty()
java_camera_manager = ObjectProperty()
java_camera_device = ObjectProperty()
java_stream_configuration_map = ObjectProperty()
_open_callback = ObjectProperty(None, allownone=True)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.register_event_type("on_opened")
self.register_event_type("on_closed")
self.register_event_type("on_disconnected")
self.register_event_type("on_error")
self._java_state_callback_runnable = Runnable(self._java_state_callback)
self._java_state_java_callback = MyStateCallback(self._java_state_callback_runnable)
self._java_capture_session_callback_runnable = Runnable(self._java_capture_session_callback)
self._java_capture_session_java_callback = MyCaptureSessionCallback(
self._java_capture_session_callback_runnable)
self._populate_camera_characteristics()
def on_opened(self, instance):
pass
def on_closed(self, instance):
pass
def on_disconnected(self, instance):
pass
def on_error(self, instance, error):
pass
def close(self):
self.java_camera_device.close()
def _populate_camera_characteristics(self):
logger.info("Populating camera characteristics")
self.java_stream_configuration_map = self.java_camera_characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
logger.info("Got stream configuration map")
self.supported_resolutions = [
(size.getWidth(), size.getHeight()) for size in
self.java_stream_configuration_map.getOutputSizes(SurfaceTexture(0).getClass())]
logger.info("Got supported resolutions")
facing = self.java_camera_characteristics.get(
CameraCharacteristics.LENS_FACING)
logger.info(f"Got facing: {facing}")
if facing == LensFacing.LENS_FACING_BACK.value: # CameraCharacteristics.LENS_FACING_BACK:
self.facing = "BACK"
elif facing == LensFacing.LENS_FACING_FRONT.value: # CameraCharacteristics.LENS_FACING_FRONT:
self.facing = "FRONT"
elif facing == LensFacing.LENS_FACING_EXTERNAL.value: # CameraCharacteristics.LENS_FACING_EXTERNAL:
self.facing = "EXTERNAL"
else:
raise ValueError("Camera id {} LENS_FACING is unknown value {}".format(self.camera_id, facing))
logger.info(f"Finished initing camera {self.camera_id}")
def __str__(self):
return "<PyCameraDevice facing={}>".format(self.facing)
def __repr__(self):
return str(self)
def open(self, callback=None):
self._open_callback = callback
self.java_camera_manager.openCamera(
self.camera_id,
self._java_state_java_callback,
_global_handler
)
def _java_state_callback(self, *args, **kwargs):
action = MyStateCallback.camera_action.toString()
camera_device = MyStateCallback.camera_device
self.java_camera_device = camera_device
logger.info("CALLBACK: camera event {}".format(action))
if action == "OPENED":
self.dispatch("on_opened", self)
self.connected = True
elif action == "DISCONNECTED":
self.dispatch("on_disconnected", self)
self.connected = False
elif action == "CLOSED":
self.dispatch("on_closed", self)
self.connected = False
elif action == "ERROR":
error = MyStateCallback.camera_error
self.dispatch("on_error", self, error)
self.connected = False
elif action == "UNKNOWN":
print("UNKNOWN camera state callback item")
self.connected = False
else:
raise ValueError("Received unknown camera action {}".format(action))
if self._open_callback is not None:
self._open_callback(self, action)
def start_preview(self, resolution):
if self.java_camera_device is None:
raise ValueError("Camera device not yet opened, cannot create preview stream")
if resolution not in self.supported_resolutions:
raise ValueError(
"Tried to open preview with resolution {}, not in supported resolutions {}".format(
resolution, self.supported_resolutions))
if self.preview_active:
raise ValueError("Preview already active, can't start again without stopping first")
logger.info("Creating capture stream with resolution {}".format(resolution))
self.preview_resolution = resolution
self._prepare_preview_fbo(resolution)
self.preview_texture = Texture(
width=resolution[0], height=resolution[1], target=GL_TEXTURE_EXTERNAL_OES, colorfmt="rgba")
logger.info("Texture id is {}".format(self.preview_texture.id))
self.java_preview_surface_texture = SurfaceTexture(int(self.preview_texture.id))
self.java_preview_surface_texture.setDefaultBufferSize(*resolution)
self.java_preview_surface = Surface(self.java_preview_surface_texture)
self.java_capture_request = self.java_camera_device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
self.java_capture_request.addTarget(self.java_preview_surface)
self.java_capture_request.set(
CaptureRequest.CONTROL_AF_MODE, ControlAfMode.CONTROL_AF_MODE_CONTINUOUS_PICTURE.value) # CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
self.java_capture_request.set(
CaptureRequest.CONTROL_AE_MODE, ControlAeMode.CONTROL_AE_MODE_ON.value) # CaptureRequest.CONTROL_AE_MODE_ON)
self.java_surface_list = ArrayList()
self.java_surface_list.add(self.java_preview_surface)
self.java_camera_device.createCaptureSession(
self.java_surface_list,
self._java_capture_session_java_callback,
_global_handler,
)
return self.preview_fbo.texture
def _prepare_preview_fbo(self, resolution):
self.preview_fbo = Fbo(size=resolution)
self.preview_fbo['resolution'] = [float(f) for f in resolution]
self.preview_fbo.shader.fs = """
#extension GL_OES_EGL_image_external : require
#ifdef GL_ES
precision highp float;
#endif
/* Outputs from the vertex shader */
varying vec4 frag_color;
varying vec2 tex_coord0;
/* uniform texture samplers */
uniform sampler2D texture0;
uniform samplerExternalOES texture1;
uniform vec2 resolution;
void main()
{
gl_FragColor = texture2D(texture1, tex_coord0);
}
"""
with self.preview_fbo:
Rectangle(size=resolution)
def _java_capture_session_callback(self, *args, **kwargs):
event = MyCaptureSessionCallback.camera_capture_event.toString()
logger.info("CALLBACK: capture event {}".format(event))
self.java_capture_session = MyCaptureSessionCallback.camera_capture_session
if event == "READY":
logger.info("Doing READY actions")
self.java_capture_session.setRepeatingRequest(self.java_capture_request.build(), None, None)
Clock.schedule_interval(self._update_preview, 0.)
def _update_preview(self, dt):
self.java_preview_surface_texture.updateTexImage()
self.preview_fbo.ask_update()
self.preview_fbo.draw()
self.output_texture = self.preview_fbo.texture
The code i want implement from camera_android to color blind
def grab_frame(self):
"""
Grab current frame (thread-safe, minimal overhead)
"""
with self._buflock:
if self._buffer is None:
return None
buf = self._buffer.tostring()
return buf
I want to make a specific table like shown in the picture below using pyqt designer and I coulnd't make a good result.
I want to make this table in a window and contains the same elements and same dimensions.
I tried to use layouts using LineEdits and Qlabels but I couldnt make it too .
Thank you.
Premise: your question didn't show lots of research efforts, and from what was said it's quite clear that you're still a bit inexperienced; this will probably make this answer very complicated, but that's because what you asked is not simple.
While achieving what it is asked is not impossible, it is not easy.
Also, you cannot do it directly in designer.
The main problem is that Qt's item views use QHeaderView, which uses a monodimensional structure; adding another "dimension" layer makes things much more difficult.
So, the first aspect you need to consider is that the table widget needs to have a new, custom QHeaderView set for the horizontal header, so you'll obviously need to subclass QHeaderView; but in order to make things work you'll also need to subclass QTableWidget too.
Due to the "monodimensionality" of the header (which only uses a single coordinate for its data), you need to "flatten" the structure and create an abstraction layer in order to access it.
In order to achieve that, I created a Structure class, with functions that allow access to it as some sort of tree-model:
class Section(object):
def __init__(self, label='', children=None, isRoot=False):
self.label = label
self._children = []
if children:
self._children = []
for child in children:
child.parent = self
self._children.append(child)
self._isRoot = isRoot
self.parent = None
def children(self):
return self._children
def isRoot(self):
return self._isRoot
def iterate(self):
# an iterator that cycles through *all* items recursively
if not self._isRoot:
yield self
items = []
for child in self._children:
items.extend([i for i in child.iterate()])
for item in items:
yield item
def sectionForColumn(self, column):
# get the first (child) item for the given column
if not self._isRoot:
return self.root().sectionForColumn(column)
for child in self.iterate():
if not child._children:
if child.column() == column:
return child
def root(self):
if self._isRoot:
return self
return self.parent.root()
def level(self):
# while levels should start from -1 (root), we're using levels starting
# from 0 (which is root); this is done for simplicity and performance
if self._isRoot:
return 0
parent = self.parent
level = 0
while parent:
level += 1
parent = parent.parent
return level
def column(self):
# root column should be -1; see comment on level()
if self._isRoot:
return 0
parentColIndex = self.parent._children.index(self)
column = self.parent.column()
for c in self.parent._children[:parentColIndex]:
column += c.columnCount()
return column
def columnCount(self):
# return the column (child) count for this section
if not self._children:
return 1
columns = 0
for child in self._children:
columns += child.columnCount()
return columns
def subLevels(self):
if not self._children:
return 0
levels = 0
for child in self._children:
levels = max(levels, child.subLevels())
return 1 + levels
class Structure(Section):
# a "root" class created just for commodity
def __init__(self, label='', children=None):
super().__init__(label, children, isRoot=True)
With this class, you can create your own header structure like this:
structure = Structure('Root item', (
Section('First parent, two sub levels', (
Section('First child, no children'),
Section('Second child, two children', (
Section('First subchild'),
Section('Second subchild')
)
)
)),
# column index = 3
Section('Second parent', (
Section('First child'),
Section('Second child')
)),
# column index = 5
Section('Third parent, no children'),
# ...
))
And here are the QHeaderView and QTableWidget subclasses, with a minimal reproducible code:
class AdvancedHeader(QtWidgets.QHeaderView):
_resizing = False
_resizeToColumnLock = False
def __init__(self, view, structure=None):
super().__init__(QtCore.Qt.Horizontal, view)
self.structure = structure or Structure()
self.sectionResized.connect(self.updateSections)
self.sectionHandleDoubleClicked.connect(self.emitHandleDoubleClicked)
def setStructure(self, structure):
if structure == self.structure:
return
self.structure = structure
self.updateGeometries()
def updateSections(self, index=0):
# ensure that the parent section is always updated
if not self.structure.children():
return
section = self.structure.sectionForColumn(index)
while not section.parent.isRoot():
section = section.parent
leftColumn = section.column()
left = self.sectionPosition(leftColumn)
width = sum(self.sectionSize(leftColumn + c) for c in range(section.columnCount()))
self.viewport().update(left - self.offset(), 0, width, self.height())
def sectionRect(self, section):
if not self.structure.children():
return
column = section.column()
left = 0
for c in range(column):
left += self.sectionSize(c)
bottom = self.height()
rowHeight = bottom / self.structure.subLevels()
if section.parent.isRoot():
top = 0
else:
top = (section.level() - 1) * rowHeight
width = sum(self.sectionSize(column + c) for c in range(section.columnCount()))
if section.children():
height = rowHeight
else:
root = section.root()
rowCount = root.subLevels()
parent = section.parent
while parent.parent:
rowCount -= 1
parent = parent.parent
height = rowHeight * rowCount
return QtCore.QRect(left, top, width, height)
def paintSubSection(self, painter, section, level, rowHeight):
sectionRect = self.sectionRect(section).adjusted(0, 0, -1, -1)
painter.drawRect(sectionRect)
painter.save()
font = painter.font()
selection = self.selectionModel()
column = section.column()
sectionColumns = set([column + c for c in range(section.columnCount())])
selectedColumns = set([i.column() for i in selection.selectedColumns()])
if ((section.children() and selectedColumns & sectionColumns == sectionColumns) or
(not section.children() and column in selectedColumns)):
font.setBold(True)
painter.setFont(font)
painter.drawText(sectionRect, QtCore.Qt.AlignCenter, section.label)
painter.restore()
for child in section.children():
self.paintSubSection(painter, child, child.level(), rowHeight)
def sectionHandleAt(self, pos):
x = pos.x() + self.offset()
visual = self.visualIndexAt(x)
if visual < 0:
return visual
for section in self.structure.iterate():
rect = self.sectionRect(section)
if pos in rect:
break
else:
return -1
grip = self.style().pixelMetric(QtWidgets.QStyle.PM_HeaderGripMargin, None, self)
if x < rect.x() + grip:
return section.column() - 1
elif x > rect.x() + rect.width() - grip:
return section.column() + section.columnCount() - 1
return -1
logical = self.logicalIndex(visual)
position = self.sectionViewportPosition(logical)
atLeft = x < (position + grip)
atRight = x > (position + self.sectionSize(logical) - grip)
if self.orientation() == QtCore.Qt.Horizontal and self.isRightToLeft():
atLeft, atRight = atRight, atLeft
if atLeft:
while visual >= 0:
visual -= 1
logical = self.logicalIndex(visual)
if not self.isSectionHidden(logical):
break
else:
logical = -1
elif not atRight:
logical = -1
return logical
def emitHandleDoubleClicked(self, index):
if self._resizeToColumnLock:
# avoid recursion
return
pos = self.viewport().mapFromGlobal(QtGui.QCursor.pos())
handle = self.sectionHandleAt(pos)
if handle != index:
return
self._resizeToColumnLock = True
for section in self.structure.iterate():
if index in range(section.column(), section.column() + section.columnCount()):
rect = self.sectionRect(section)
if rect.y() <= pos.y() <= rect.y() + rect.height():
sectCol = section.column()
for col in range(sectCol, sectCol + section.columnCount()):
if col == index:
continue
self.sectionHandleDoubleClicked.emit(col)
break
self._resizeToColumnLock = False
# -------- base class reimplementations -------- #
def sizeHint(self):
hint = super().sizeHint()
hint.setHeight(hint.height() * self.structure.subLevels())
return hint
def mousePressEvent(self, event):
super().mousePressEvent(event)
if event.button() != QtCore.Qt.LeftButton:
return
handle = self.sectionHandleAt(event.pos())
if handle >= 0:
self._resizing = True
else:
# if the clicked section has children, select all of its columns
cols = []
for section in self.structure.iterate():
sectionRect = self.sectionRect(section)
if event.pos() in sectionRect:
firstColumn = section.column()
columnCount = section.columnCount()
for column in range(firstColumn, firstColumn + columnCount):
cols.append(column)
break
self.sectionPressed.emit(cols[0])
for col in cols[1:]:
self.sectionEntered.emit(col)
def mouseMoveEvent(self, event):
super().mouseMoveEvent(event)
handle = self.sectionHandleAt(event.pos())
if not event.buttons():
if handle < 0:
self.unsetCursor()
elif handle < 0 and not self._resizing:
# update sections when click/dragging (required if highlight is enabled)
pos = event.pos()
pos.setX(pos.x() + self.offset())
for section in self.structure.iterate():
if pos in self.sectionRect(section):
self.updateSections(section.column())
break
# unset the cursor, in case it was set for a section handle
self.unsetCursor()
def mouseReleaseEvent(self, event):
self._resizing = False
super().mouseReleaseEvent(event)
def paintEvent(self, event):
qp = QtGui.QPainter(self.viewport())
qp.setRenderHints(qp.Antialiasing)
qp.translate(.5, .5)
height = self.height()
rowHeight = height / self.structure.subLevels()
qp.translate(-self.horizontalOffset(), 0)
column = 0
for parent in self.structure.children():
self.paintSubSection(qp, parent, 0, rowHeight)
column += 1
class CustomHeaderTableWidget(QtWidgets.QTableWidget):
structure = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
customHeader = AdvancedHeader(self)
self.setHorizontalHeader(customHeader)
customHeader.setSectionsClickable(True)
customHeader.setHighlightSections(True)
self.cornerHeader = QtWidgets.QLabel(self)
self.cornerHeader.setAlignment(QtCore.Qt.AlignCenter)
self.cornerHeader.setStyleSheet('border: 1px solid black;')
self.cornerHeader.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
self.verticalHeader().setMinimumWidth(
self.cornerHeader.minimumSizeHint().width() + self.fontMetrics().width(' '))
self._cornerButton = self.findChild(QtWidgets.QAbstractButton)
self.setStructure(kwargs.get('structure') or Section('ROOT', isRoot=True))
self.selectionModel().selectionChanged.connect(self.selectionModelSelChanged)
def setStructure(self, structure):
if structure == self.structure:
return
self.structure = structure
if not structure:
super().setColumnCount(0)
self.cornerHeader.setText('')
else:
super().setColumnCount(structure.columnCount())
self.cornerHeader.setText(structure.label)
self.horizontalHeader().setStructure(structure)
self.updateGeometries()
def selectionModelSelChanged(self):
# update the corner widget
selected = len(self.selectionModel().selectedIndexes())
count = self.model().rowCount() * self.model().columnCount()
font = self.cornerHeader.font()
font.setBold(selected == count)
self.cornerHeader.setFont(font)
def updateGeometries(self):
super().updateGeometries()
vHeader = self.verticalHeader()
if not vHeader.isVisible():
return
style = self.verticalHeader().style()
opt = QtWidgets.QStyleOptionHeader()
opt.initFrom(vHeader)
margin = style.pixelMetric(style.PM_HeaderMargin, opt, vHeader)
width = self.cornerHeader.minimumSizeHint().width() + margin * 2
vHeader.setMinimumWidth(width)
self.cornerHeader.setGeometry(self._cornerButton.geometry())
def setColumnCount(self, count):
# ignore column count, as we're using setStructure() instead
pass
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
structure = Structure('UNITE', (
Section('Hrs de marche', (
Section('Expl'),
Section('Indi', (
Section('Prev'),
Section('Accid')
))
)),
Section('Dem', (
Section('TST'),
Section('Epl')
)),
Section('Decle'),
Section('a'),
Section('Consom'),
Section('Huile'),
))
tableWidget = CustomHeaderTableWidget()
tableWidget.setStructure(structure)
tableWidget.setRowCount(2)
tableWidget.setVerticalHeaderLabels(
['Row {}'.format(r + 1) for r in range(tableWidget.rowCount())])
tableWidget.show()
sys.exit(app.exec())
Some considerations, since the above example is not perfect:
sections are not movable (if you try to set setSectionsMovable and try to drag a section, it would probably crash at some point);
while I tried to avoid resizing of a "parent" section (the resize cursor is not shown), it is still possible to resize a child section from the parent rectangle;
changing the horizontal structure of the model might give unexpected results (I only implemented basic operations);
Structure is a standard python object subclass, and it's completely unlinked from the QTableWidget;
considering the above, using functions like horizontalHeaderItem, setHorizontalHeaderItem or setHorizontalHeaderLabels might not work as expected;
Now, how to use it in designer? You need to use a promoted widget.
Add the QTableWidget, right click on it and select Promote to..., ensure that "QTableWidget" is selected in the "Base class name" combo, type "CustomHeaderTableWidget" in the "Promoted class name" field and then the file name that contains the subclass in the "Header file" field (note that it's treated like a python module name, so it has to be without the .py file extension); click "Add", click "Promote" and save it.
Consider that, from there, you must still provide the custom Structure, and if you added any row and column in Designer it must reflect the structure column count.
Finally, since the matter is interesting, I might return on it in the future and update the code, eventually.
In the meantime, I strongly suggest you to carefully study the code, explore all the reimplementations of QHeaderView (see what's below base class reimplementations comment) and what the original methods actually do by reading the QHeaderView documentation.
I am working with PyQt5/PySide2. I Have a QTableView with QSortFilterProxyModel and the data is handled by QStandardItemModel.
I am using QStandardItemModel.findItems() method to find some cells in the first table row. The result is a list of QStandardItems. Now I want to order these items by row in which they are displayed in GUI table i.e. the way that user sees them. Is there some way to archive this?. To translate proxy or model indices to to "view" indices.
I tought this could be done using QSortFilterProxyModel.mapFromSource() method but it appears the proxy indices do not have the desired order.
here is a minimal reproducible example written in PyQt5:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from collections import deque
from random import randint
class Splash(QWidget):
def __init__(self):
super().__init__()
# create model
self.model = QStandardItemModel(self)
self.model.setHorizontalHeaderLabels(["column 1", "column 2"])
# create sort proxy
self.proxy = NumberSortModel()
self.proxy.setSourceModel(self.model)
# create view
self.table = CustomQTableView(self)
self.table.setGeometry(0, 0, 275, 575)
self.table.setModel(self.proxy)
self.table.setSortingEnabled(True)
# create buttons
button = QPushButton('Find cells containing 1', self)
button.move(300, 70)
button.clicked.connect(lambda: self.table.search_string("1"))
button1 = QPushButton('next', self)
button1.move(300, 100)
button1.clicked.connect(self.table._search_next)
button2 = QPushButton('previous', self)
button2.move(300, 130)
button2.clicked.connect(self.table._search_previous)
# fill model
for i in range(15):
self.model.appendRow([QStandardItem(str(i)),
QStandardItem(str(randint(1, 100)))])
self.show()
# takes care of the coloring of results
class _HighlightDelegate(QStyledItemDelegate):
def __init__(self, parent=None) -> None:
QStyledItemDelegate.__init__(self, parent)
self._parent = parent
def paint(self, painter: "QPainter", option: "QStyleOptionViewItem",
index: "QModelIndex"):
painter.save()
if len(self._parent.proxy_indices) > 0:
if index == self._parent.proxy_indices[0]:
painter.fillRect(option.rect, Qt.red)
elif index in self._parent.proxy_indices:
painter.fillRect(option.rect, option.palette.highlight())
else:
if (option.state & QStyle.State_Selected):
painter.fillRect(option.rect, option.palette.highlight())
elif (option.state & QStyle.State_None):
painter.fillRect(option.rect, option.palette.base())
painter.drawText(option.rect, Qt.AlignLeft, index.data(Qt.DisplayRole))
painter.restore()
class CustomQTableView(QTableView):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.real_indices = deque()
self.proxy_indices = deque()
self.horizontalHeader().sortIndicatorChanged.connect(self._re_sort)
self.setItemDelegate(_HighlightDelegate(self))
def _re_sort(self):
# pretty print indices
def ind_to_py(indices):
py_ind = list()
for i in indices:
py_ind.append((i.row(), i.column(), i.data(Qt.DisplayRole)))
return py_ind
print("real ", ind_to_py(self.real_indices))
print("proxy ", ind_to_py(self.proxy_indices))
real_ind, proxy_ind = zip(*sorted(zip(self.real_indices, self.proxy_indices),
key=lambda x: (x[1].row(),
x[1].column())))
self.real_indices = deque(real_ind)
self.proxy_indices = deque(proxy_ind)
print("sorted real ", ind_to_py(self.real_indices))
print("sorted proxy", ind_to_py(self.proxy_indices))
print("---------------------------------------------------")
self.re_draw()
#property
def _model(self):
return self.model().sourceModel()
def re_draw(self):
self.viewport().update()
# we are always searching only in first column
def search_string(self, string: str):
indices = self._model.findItems(string, Qt.MatchContains, 0)
# get QModelIndex from found data
self.real_indices = deque([i.index() for i in indices])
self.proxy_indices = [QPersistentModelIndex(self.model().mapFromSource(i))
for i in self.real_indices]
# sort indeces according to their row and column
self._re_sort()
# update the view to highlight data
self.re_draw()
def _search_next(self):
self.real_indices.rotate(-1)
self.proxy_indices.rotate(-1)
self.re_draw()
def _search_previous(self):
self.real_indices.rotate(1)
self.proxy_indices.rotate(1)
self.re_draw()
# custom implementation to sort according to numbers not strings
class NumberSortModel(QSortFilterProxyModel):
def lessThan(self, left_index: "QModelIndex",
right_index: "QModelIndex") -> bool:
left_var: str = left_index.data(Qt.EditRole)
right_var: str = right_index.data(Qt.EditRole)
try:
return float(left_var) < float(right_var)
except (ValueError, TypeError):
pass
try:
return left_var < right_var
except TypeError: # in case of NoneType
return True
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
ex = Splash()
sys.exit(app.exec_())
In short when I run the search and click next the red marked cell moves down. It moves up when previous is clicked. But when I apply sorting by clicking table header it is messes up next/previous funstions. I want the red cell to always go down irrespective of applied sorting when next is clicked same with previous.
The logic of the painting should not be done directly but through the roles that must be used by the delegate to do the painting.
On the other hand, the following item must be selected based on the visual position of the item, so it must be mapped using the proxy.
Considering the above, the solution is:
CurrentRole = Qt.UserRole + 1000
SelectedRole = Qt.UserRole + 1001
# takes care of the coloring of results
class _HighlightDelegate(QStyledItemDelegate):
def initStyleOption(self, option: "QStyleOptionViewItem", index: "QModelIndex"):
super().initStyleOption(option, index)
is_current = index.data(CurrentRole) or False
is_selected = index.data(SelectedRole) or False
if is_current:
option.backgroundBrush = QColor(Qt.red)
option.palette.setColor(QPalette.Normal, QPalette.Highlight, QColor(Qt.red))
elif is_selected:
option.backgroundBrush = option.palette.highlight()
class CustomQTableView(QTableView):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.selected_indexes = []
self.current_index = None
self.setItemDelegate(_HighlightDelegate(self))
#property
def _model(self):
return self.model().sourceModel()
def search_string(self, string: str):
# restore
for index in self.selected_indexes:
self._model.setData(QModelIndex(index), False, SelectedRole)
if self.current_index is not None:
self._model.setData(QModelIndex(self.current_index), False, CurrentRole)
self.current_index = None
column = 0
match_indexes = self._model.match(
self._model.index(0, column), Qt.DisplayRole, string, -1, Qt.MatchContains
)
self.selected_indexes = [
QPersistentModelIndex(index) for index in match_indexes
]
self._sort_indexes_by_view()
if self.selected_indexes:
self.current_index = self.selected_indexes[0]
for index in self.selected_indexes:
self._model.setData(QModelIndex(index), True, SelectedRole)
if self.current_index is not None:
self._model.setData(QModelIndex(self.current_index), True, CurrentRole)
def _search_next(self):
if self.current_index is not None:
self._model.setData(QModelIndex(self.current_index), False, CurrentRole)
self._sort_indexes_by_view()
pos = self.selected_indexes.index(self.current_index)
next_pos = (pos + 1) % len(self.selected_indexes)
self.current_index = self.selected_indexes[next_pos]
self._model.setData(QModelIndex(self.current_index), True, CurrentRole)
def _search_previous(self):
if self.current_index is not None:
self._model.setData(QModelIndex(self.current_index), False, CurrentRole)
self._sort_indexes_by_view()
pos = self.selected_indexes.index(self.current_index)
next_pos = (pos - 1) % len(self.selected_indexes)
self.current_index = self.selected_indexes[next_pos]
self._model.setData(QModelIndex(self.current_index), True, CurrentRole)
def _sort_indexes_by_view(self):
self.selected_indexes.sort(
key=lambda index: self.model().mapFromSource(QModelIndex(index)).row()
)
I'm trying to rewrite a script and I'm stuck on making it easy to use. Basically it's an assembly script (like the reverse of destruction), where you input a load of variables such as location, whether the location is absolute or relative, scale, rotation, visibility, random offset, etc, to create an animation. The first version was very non user friendly, so I'm trying to get it working nicely from the start this time.
I've thought of how I'd like it to work, and I've managed to keep it clean, but there is a flaw. As you can see below, it'd be possible to use anything like SetGroup.frame[i].save(), which I don't want (and I don't want to put checks on if name is None throughout the class).
Here is the code I have:
class SetGroup(object):
def __init__(self, name=None, _frame_only=False):
if name is None and not _frame_only:
raise TypeError('name of group must be provided')
self.selection = None
self.origin = None
self.start = None
self.end = None
self.offset = 0
self.distance = None
self.random = 0
self.location = None
self.rotation = None
self.scale = None
self.visibility = None
if not _frame_only:
self.frame = defaultdict(lambda: SetGroup(_frame_only=True))
def save(self):
self.load()
#do a bit of error checking here
self.data[self.name] = {'ObjectSelection': self.selection,
'ObjectOrigin': self.origin,
'FrameStart': self.start,
'FrameEnd': self.end,
'FrameOffset': self.offset,
'FrameDistance': self.distance,
'FrameRandom': self.random,
'StartLocation': self.location,
'StartRotation': self.rotation,
'StartScale': self.scale,
'StartVisibility': self.visibility,
'ExtraFrames': self.frame}
pm.fileInfo['AssemblyScript'] = StoreData().save(self.data)
def load(self):
try:
self.data = StoreData().load(pm.fileInfo['AssemblyScript'])
except KeyError:
pm.fileInfo['AssemblyScript'] = StoreData().save({})
The way I'd like it to work is like this:
a = SetGroup('test')
a.location = ((0, 0, 0), True)
a.start = 0
a.end = 10
a.frame[5].location = ((10, 10, 10), False)
a.frame[5].scale = ((2, 1, 1), True)
a.save()
Unless anyone can think of a way which would make it more friendly to use, how would I separate location, rotation, scale, and visibility into another class and link them up again, so that they still work at the core level of the class, but also work for the frame dictionary too?
Edit - Got it working to a basic level:
class _MovementInfo(object):
def __init__(self, location=None, rotation=None, scale=None, visibility=None):
self.location = location
self.rotation = rotation
self.scale = scale
self.visibility = visibility
def __repr__(self):
return '_MovementInfo(location={x.location}, rotation={x.rotation}, scale={x.scale}, visibility={x.visibility}'.format(x=self)
Then I used this in the main class to merge the dictionaries:
self.__dict__.update({k: v for k, v in _MovementInfo().__dict__.iteritems() if '__' not in k})
self.frame = defaultdict(_MovementInfo)
I would change the code like this:
class SetGroup(_Movement):
def __init__(self, name=None):
if name is None:
# ...
super().__init__()
# ...
self.random = 0 # __init__ should end here
# ...
But you should check that all _MovementInfo's in all frames are _MovementInfo's or have inherited from them (to check this: isinstance(x, _MovementInfo)), but are not SetGroup's (to check this: not isinstance(x, SetGroup)).
super() is short for super(SetGroup, self) (you have to use the last option for python2), and is basicly an object that holds all things that the base class has, and allows you to call methods that modify the class calling it.
Or in code:
class A(object):
def __init__(self, y):
self.x = 2
self.y = y
class B(A):
def __init__(self, y, z):
super().__init__(y) # equivalent to: A.__init__(self, y)
self.z = z
b = B(3, 4)
# b's x is 2, b's y is 3 (both set by A.__init__, the last one was passed by B), and b's z is 4 (set by B.__init__)
I hope this helped,
CodenameLambda