Open Harvie opened 7 years ago
I am doing the 3D rendering manually (not using any 3d library) and python is not very fast on that. I could potentially do it but it wont be interactive.
Is there reason for that? I guess there are well tested OpenGL bindings for python, or something like that...
Yes, bCNC is not using OpenGL, it uses the minimum on external dependencies.
I know, but is there sensible reason for that? This "not invented here" approach is not always that wise. Especialy when it not only lacks basic rotating functionality, but you obviously run to performance problems (timeouting renders), etc...
BTW i've made linux packages for like hundred programs, so i know how annoying external dependencies can be. But sometimes it makes sense to use few external libs. Especialy when they are widely common and already packaged. It's about some kind of balance. You don't want dependency hell of 100 external libs, but using something as common as opengl for 3d rendering makes some sense...
I would be more than happy if you could help in adding OpenGL as a rendered.
Do you know which methods need to be ported to OpenGL?
Just accidentaly found this: https://docs.enthought.com/mayavi/mayavi/ Not sure if that can be reused as display in bCNC...
VisPy seems to have all we need, but i can't figure out how to integrate it to the tkinter window :-(
http://vispy.org https://github.com/vispy/vispy https://glumpy.github.io/ https://github.com/glumpy/glumpy https://kivy.org/doc/stable/examples/gen__3Drendering__main__py.html
Maybe GL glue can put this together? https://github.com/ousttrue/glglue
Maybe GLUT or PyGame?
Seems to me that Tkinter is horrible choice for writing GL applications. We'll have to reinvent some basics to get it working.
Basicaly only way of having OpenGL without abandoning Tkinter is using some 10 years old library called Togl, which is b*tch to install and distribute. Such a PITA.
http://pyopengl.sourceforge.net/pydoc/OpenGL.Tk.html says:
Traditional PyOpenGL interface to Togl
This module provides access to the Tkinter Togl widget with a relatively "thick" wrapper API that creates a set of default "examination" operations.
Note that many (most) Linux distributions have now split out the Tkinter bindings into a separate package, and that Togl must be separately downloaded (a script is provided in the source distribution which downloads and installs Togl 2.0 binaries for you).
Because of the complexity and fragility of the installation, it is recommended that you use Pygame, wxPython, PyGtk, or PyQt for real-world projects, and GLUT or Pygame for simple demo/testing interfaces.
The Togl project is located here: http://togl.sourceforge.net/
https://stackoverflow.com/questions/8584272/using-pygame-features-in-tkinter This seems to enable us to embed pygame into tkinter. Probably best we can get with tkinter.
import Tkinter as tk
import os
w, h = 500, 200
# Add a couple widgets. We're going to put pygame in `embed`.
root = tk.Tk()
embed = tk.Frame(root, width=w, height=h)
embed.pack()
text = tk.Button(root, text='Blah.')
text.pack()
# Tell pygame's SDL window which window ID to use
os.environ['SDL_WINDOWID'] = str(embed.winfo_id())
# The wxPython wiki says you might need the following line on Windows
# (http://wiki.wxpython.org/IntegratingPyGame).
#os.environ['SDL_VIDEODRIVER'] = 'windib'
# Show the window so it's assigned an ID.
root.update()
# Usual pygame initialization
import pygame as pg
pg.display.init()
screen = pg.display.set_mode((w,h))
pos = 0
while 1:
# Do some pygame stuff
screen.fill(pg.Color(0,0,0))
pos = (pos + 1) % screen.get_width()
pg.draw.circle(screen, pg.Color(255,255,255), (pos,100), 30)
# Update the pygame display
pg.display.flip()
# Update the Tk display
root.update()
That basicaly means, you can include any SDL or PyGame application into Tkinter Frame by setting env SDL_WINDOWID to Frame.winfo_id()
Eg.: here i received the window id of bCNC canvas using
print("canvas wid",self.canvasFrame.canvas.winfo_id())
and then launched simple SDL game with that ID, which resulted in embeding of that game into bCNC. However mouse/keyboard inputs are still going to bCNC, so the game is unplayable.
Also i am not sure, if this will work on windows or mac.
Does the Tkinter canvas has to be slow? This seems to be relatively fast:
https://www.youtube.com/watch?v=rkNToAMcIcs
But it does not have antialiasing, which sucks...
This does realtime tkinter canvas rotation using mouse and does not seem too slow in my opinion:
https://github.com/ActiveState/code/tree/master/recipes/Python/578876_Rotatable_Tetrahedron http://code.activestate.com/recipes/578876-rotatable-tetrahedron/
#!/usr/bin/env python
# coding: UTF-8
#
## @package _12_tet
#
# Draws a 3D tetrahedron and allows a user to rotate it
# (mouse left button and wheel).
#
# The Tetrahedron is represented by a 3 x 4 matrix.
# Each column represents a 3D vertex.
#
# note: a m x n matrix is represented by a list of lines:
# [[l_1] [l_2] .. [l_m]].
# m = len(mat), n = len(mat[0]), mat(i,j) = mat[i][j]
#
# @author Paulo Roma
# @since 01/05/2014
# @see http://www.orimosenzon.com/wiki/index.php/Python_samples
# @see http://mathworld.wolfram.com/RotationMatrix.html
try:
from tkinter import * # python 3
except ImportError:
from Tkinter import * # python 2
from math import *
def createZeroMat(m,n):
"""Return a matrix (m x n) filled with zeros."""
ret = [0] * m
for i in range(m):
ret[i] = [0] * n
return ret
def matMul(mat1, mat2):
"""Return mat1 x mat2 (mat1 multiplied by mat2)."""
m = len(mat1)
n = len(mat2[0])
common = len(mat2)
ret = createZeroMat(m,n)
if len(mat1[0]) == len(mat2):
for i in range(m):
for j in range(n):
for k in range(common):
ret[i][j] += mat1[i][k] * mat2[k][j]
return ret
def matTrans(mat):
"""Return mat (n x m) transposed (m x n)."""
m = len(mat[0])
n = len(mat)
ret = createZeroMat(m,n)
for i in range(m):
for j in range(n):
ret[i][j] = mat[j][i]
return ret
def translate(x,y,dx,dy):
"""Translate vector(x,y) by (dx,dy)."""
return x+dx, y+dy
def drawTet(tet,col):
"""Draw a tetrahedron."""
w = canvas.winfo_width()/2
h = canvas.winfo_height()/2
canvas.delete(ALL) # delete all edges
nv = len(tet[0]) # number of vertices in tet (4)
# draw the 6 edges of the tetrahedron
for p1 in range(nv):
for p2 in range(p1+1,nv):
canvas.create_line(translate(tet[0][p1], tet[1][p1], w, h),
translate(tet[0][p2], tet[1][p2], w, h), fill = col)
def init():
"""Initialize global variables."""
global ROT_X, ROT_Y, ROT_Z
global eps, EPS, tet
global lastX, lastY, tetColor, bgColor
tet = matTrans([[0,-100,0],[-100,100,0],[100,100,0],[0,0,200]])
# counter-clockwise rotation about the X axis
ROT_X = lambda x: matTrans([[1,0,0], [0,cos(x),-sin(x)], [0,sin(x),cos(x)] ])
# counter-clockwise rotation about the Y axis
ROT_Y = lambda y: matTrans([[cos(y),0,sin(y)], [0,1,0], [-sin(y),0,cos(y)]])
# counter-clockwise rotation about the Z axis
ROT_Z = lambda z: matTrans([[cos(z),sin(z),0], [-sin(z),cos(z),0], [0,0,1]])
eps = lambda d: pi/300 if (d>0) else -pi/300
EPS = lambda d: d*pi/300
lastX = 0
lastY = 0
tetColor = 'black'
bgColor = 'white'
def cbClicked(event):
"""Save current mouse position."""
global lastX, lastY
lastX = event.x
lastY = event.y
def cbMottion(event):
"""Map mouse displacements in Y direction to rotations about X axis,
and mouse displacements in X direction to rotations about Y axis."""
global tet
# Y coordinate is upside down
dx = lastY - event.y
tet = matMul(ROT_X(EPS(-dx)),tet)
dy = lastX - event.x
tet = matMul(ROT_Y(EPS(dy)),tet)
drawTet(tet,tetColor)
cbClicked(event)
def wheelUp(event):
"""Map mouse wheel up displacements to rotations about Z axis."""
global tet
tet = matMul(ROT_Z(EPS(1)),tet)
drawTet(tet,tetColor)
def wheelDown(event):
"""Map mouse wheel down displacements to rotations about Z axis."""
global tet
tet = matMul(ROT_Z(EPS(-1)),tet)
drawTet(tet,tetColor)
def wheel(event):
"""Map mouse wheel displacements to rotations about Z axis."""
global tet
tet = matMul(ROT_Z(EPS(event.delta/120)),tet)
drawTet(tet,tetColor)
def resize(event):
"""Redraw the tetrahedron, in case of a window change due to user resizing it."""
drawTet(tet,tetColor)
def main():
global canvas
root = Tk()
root.title('Tetrahedron')
root.geometry('+0+0')
init()
canvas = Canvas(root, width=400, height=400, background=bgColor)
canvas.pack(fill=BOTH,expand=YES)
canvas.bind("<Button-1>", cbClicked)
canvas.bind("<B1-Motion>", cbMottion)
canvas.bind("<Configure>", resize)
from platform import uname
os = uname()[0]
if ( os == "Linux" ):
canvas.bind('<Button-4>', wheelUp) # X11
canvas.bind('<Button-5>', wheelDown)
elif ( os == "Darwin" ):
canvas.bind('<MouseWheel>', wheel) # MacOS
else:
canvas.bind_all('<MouseWheel>', wheel) # windows
drawTet(tet,tetColor)
mainloop()
if __name__=='__main__':
sys.exit(main())
Just tried add more 3D lines to that code. It seems to be reasonably fast when working with 10000 3D lines, and i would say that up to 3000 lines it runs almost smoothly. But still... No way to get antialiasing :-(
Another solution may be to rewrite bCNC to GTK3 (PyGTK) or wxWidgets (wxPython), which are more up to date than Tkinter and have no troubles with OpenGL. But that would be at least labour intensive.
Just tried this and it looks very promissing:
https://github.com/jonwright/pyopengltk
I have OpenGL context in Tkinter window without any hacks!
Looks very promising!! Good work.
It can be really usefull to be able to freely rotate 3D view by mouse draging. The predefined ISO1-3 views are kinda doing the job, but it helps to see objects in slight movement to get faster grasp of their shapes.