Commit 0cd29256 authored by sumpfralle's avatar sumpfralle

moved OpenGL code to OpenGLTools

moved more matrix functions to pycam.Geometry.Matrix
moved pycam.Gui.ode_objects to pycam.Physics.ode_physics


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@321 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent d2f12762
......@@ -28,7 +28,7 @@ class CylindricalCutter(BaseCutter):
def get_shape(self, format="ODE"):
if format == "ODE":
import ode
import pycam.Gui.ode_objects
import pycam.Physics.ode_physics
""" We don't handle the the "additional_distance" perfectly, since
the "right" shape would be a cylinder with a small flat cap that
grows to the full expanded radius through a partial sphere. The
......@@ -70,7 +70,7 @@ class CylindricalCutter(BaseCutter):
rot_matrix_box = (cosinus, sinus, 0.0, -sinus, cosinus, 0.0, 0.0, 0.0, 1.0)
geom_connect_transform = ode.GeomTransform(geom.space)
geom_connect_transform.setBody(geom.getBody())
geom_connect = pycam.Gui.ode_objects.get_parallelepiped_geom(
geom_connect = pycam.Physics.ode_physics.get_parallelepiped_geom(
(Point(-hypotenuse / 2.0, radius, -diff_z / 2.0), Point(hypotenuse / 2.0, radius, diff_z / 2.0),
Point(hypotenuse / 2.0, -radius, diff_z / 2.0), Point(-hypotenuse / 2.0, -radius, -diff_z / 2.0)),
(Point(-hypotenuse / 2.0, radius, self.height - diff_z / 2.0), Point(hypotenuse / 2.0, radius, self.height + diff_z / 2.0),
......
......@@ -28,7 +28,7 @@ class SphericalCutter(BaseCutter):
def get_shape(self, format="ODE"):
if format == "ODE":
import ode
import pycam.Gui.ode_objects
import pycam.Physics.ode_physics
additional_distance = self.get_required_distance()
radius = self.radius + additional_distance
center_height = 0.5 * self.height + radius - additional_distance
......@@ -61,7 +61,7 @@ class SphericalCutter(BaseCutter):
rot_matrix_box = (cosinus, sinus, 0.0, -sinus, cosinus, 0.0, 0.0, 0.0, 1.0)
geom_connect_transform = ode.GeomTransform(geom.space)
geom_connect_transform.setBody(geom.getBody())
geom_connect = pycam.Gui.ode_objects.get_parallelepiped_geom(
geom_connect = pycam.Physics.ode_physics.get_parallelepiped_geom(
(Point(-hypotenuse / 2.0, radius, -diff_z / 2.0), Point(hypotenuse / 2.0, radius, diff_z / 2.0),
Point(hypotenuse / 2.0, -radius, diff_z / 2.0), Point(-hypotenuse / 2.0, -radius, -diff_z / 2.0)),
(Point(-hypotenuse / 2.0, radius, self.height - diff_z / 2.0), Point(hypotenuse / 2.0, radius, self.height + diff_z / 2.0),
......@@ -77,7 +77,7 @@ class SphericalCutter(BaseCutter):
# rotate cylinder vector
cyl_original_vector = (0, 0, hypotenuse_3d)
cyl_destination_vector = (diff_x, diff_y, diff_z)
geom_cyl.setRotation(Matrix.get_rotation_matrix(cyl_original_vector, cyl_destination_vector))
geom_cyl.setRotation(Matrix.get_rotation_matrix_from_to(cyl_original_vector, cyl_destination_vector))
# the rotation is around the center - thus we ignore negative diff values
geom_cyl.setPosition((abs(diff_x / 2.0), abs(diff_y / 2.0), radius - additional_distance))
geom_cyl_transform.setGeom(geom_cyl)
......
......@@ -6,9 +6,27 @@ from pycam.Geometry.Point import Point
import math
def get_dot_product(a, b):
""" calculate the dot product of two 3d vectors
@type a: tuple(float) | list(float)
@value a: the first vector to be multiplied
@type b: tuple(float) | list(float)
@value b: the second vector to be multiplied
@rtype: float
@return: the dot product is (a0*b0 + a1*b1 + a2*b2)
"""
return sum(map(lambda l1, l2: l1 * l2, a, b))
def get_cross_product(a, b):
""" calculate the cross product of two 3d vectors
@type a: tuple(float) | list(float) | pycam.Geometry.Point
@value a: the first vector to be multiplied
@type b: tuple(float) | list(float) | pycam.Geometry.Point
@value b: the second vector to be multiplied
@rtype: tuple(float)
@return: the cross product is a 3d vector
"""
if isinstance(a, Point):
a = (a.x, a.y, a.z)
if isinstance(b, Point):
......@@ -18,9 +36,36 @@ def get_cross_product(a, b):
a[0] * b[1] - a[1] * b[0])
def get_length(vector):
""" calculate the lengt of a 3d vector
@type vector: tuple(float) | list(float)
@value vector: the given 3d vector
@rtype: float
@return: the length of a vector is the square root of the dot product
of the vector with itself
"""
return math.sqrt(get_dot_product(vector, vector))
def get_rotation_matrix(v_orig, v_dest):
def get_rotation_matrix_from_to(v_orig, v_dest):
""" calculate the rotation matrix used to transform one vector into another
The result is useful for modifying the rotation matrix of a 3d object.
See the "extend_shape" code in each of the cutter classes (for ODE).
The simplest example is the following with the original vector pointing
along the x axis, while the destination vectors goes along the y axis:
get_rotation_matrix((1, 0, 0), (0, 1, 0))
Basically this describes a rotation around the z axis by 90 degrees.
The resulting 3x3 matrix (tuple of 9 floats) can be multiplied with any
other vector to rotate it in the same way around the z axis.
@type v_orig: tuple(float) | list(float) | pycam.Geometry.Point
@value v_orig: the original 3d vector
@type v_dest: tuple(float) | list(float) | pycam.Geometry.Point
@value v_dest: the destination 3d vector
@rtype: tuple(float)
@return: the tuple of 9 floats represents a 3x3 matrix, that can be
multiplied with any vector to rotate it in the same way, as you would
rotate v_orig to the position of v_dest
"""
if isinstance(v_orig, Point):
v_orig = (v_orig.x, v_orig.y, v_orig.z)
if isinstance(v_dest, Point):
......@@ -50,3 +95,41 @@ def get_rotation_matrix(v_orig, v_dest):
t * rot_axis.y * rot_axis.z + s * rot_axis.x,
t * rot_axis.z * rot_axis.z + c)
def get_rotation_matrix_axis_angle(rot_axis, rot_angle):
""" calculate rotation matrix for a normalized "rot_axis" vector and an angle
see http://mathworld.wolfram.com/RotationMatrix.html
@type rot_axis: tuple(float)
@value rot_axis: the vector describes the rotation axis. Its length should
be 1.0 (normalized).
@type rot_angle: float
@value rot_angle: rotation angle (radiant)
@rtype: tuple(float)
@return: the roation
"""
sin = math.sin(rot_angle)
cos = math.cos(rot_angle)
return ((cos + rot_axis[0]*rot_axis[0]*(1-cos),
rot_axis[0]*rot_axis[1]*(1-cos) - rot_axis[2]*sin,
rot_axis[0]*rot_axis[2]*(1-cos) + rot_axis[1]*sin),
(rot_axis[1]*rot_axis[0]*(1-cos) + rot_axis[2]*sin,
cos + rot_axis[1]*rot_axis[1]*(1-cos),
rot_axis[1]*rot_axis[2]*(1-cos) - rot_axis[0]*sin),
(rot_axis[2]*rot_axis[0]*(1-cos) - rot_axis[1]*sin,
rot_axis[2]*rot_axis[1]*(1-cos) + rot_axis[0]*sin,
cos + rot_axis[2]*rot_axis[2]*(1-cos)))
def multiply_vector_matrix(v, m):
""" Multiply a 3d vector with a 3x3 matrix. The result is a 3d vector.
@type v: tuple(float) | list(float)
@value v: a 3d vector as tuple or list containing three floats
@type m: tuple(tuple(float)) | list(list(float))
@value m: a 3x3 list/tuple of floats
@rtype: tuple(float)
@return: a tuple of 3 floats as the matrix product
"""
return (v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2],
v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2],
v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2])
from pycam.Geometry.Point import Point
import pycam.Geometry.Matrix as Matrix
import OpenGL.GL as GL
import OpenGL.GLU as GLU
import OpenGL.GLUT as GLUT
import gtk
import pango
import math
import time
# buttons for rotating, moving and zooming the model view window
BUTTON_ROTATE = gtk.gdk.BUTTON1_MASK
BUTTON_MOVE = gtk.gdk.BUTTON2_MASK
BUTTON_ZOOM = gtk.gdk.BUTTON3_MASK
# the length of the distance vector does not matter - it will be normalized and multiplied later anyway
VIEWS = {
......@@ -15,26 +24,6 @@ VIEWS = {
"back": {"distance": (0.0, 1.0, 0.0), "center": (0.0, 0.0, 0.0), "up": (0.0, 0.0, 1.0), "znear": 0.1, "zfar": 1000.0, "fovy": 30.0},
}
def rotate(orig, rot_axis, sin, cos):
""" rotation of an original vector around a normalized "rot_axis" vector
see http://mathworld.wolfram.com/RotationMatrix.html
@type orig: tuple(float)
@value orig: the vector to be rotated
@type rot_axis: tuple(float)
@value rot_axis: the vector describes the rotation axis
@type sin: float
@value sin: sinus of the rotation angle
@type cos: float
@value cos: cosinus of the rotation angle
@rtype: tuple(float)
"""
rot_matrix = ((cos + rot_axis[0]*rot_axis[0]*(1-cos), rot_axis[0]*rot_axis[1]*(1-cos) - rot_axis[2]*sin, rot_axis[0]*rot_axis[2]*(1-cos) + rot_axis[1]*sin),
(rot_axis[1]*rot_axis[0]*(1-cos) + rot_axis[2]*sin, cos + rot_axis[1]*rot_axis[1]*(1-cos), rot_axis[1]*rot_axis[2]*(1-cos) - rot_axis[0]*sin),
(rot_axis[2]*rot_axis[0]*(1-cos) - rot_axis[1]*sin, rot_axis[2]*rot_axis[1]*(1-cos) + rot_axis[0]*sin, cos + rot_axis[2]*rot_axis[2]*(1-cos)))
return (orig[0]*rot_matrix[0][0] + orig[1]*rot_matrix[0][1] + orig[2]*rot_matrix[0][2],
orig[0]*rot_matrix[1][0] + orig[1]*rot_matrix[1][1] + orig[2]*rot_matrix[1][2],
orig[0]*rot_matrix[2][0] + orig[1]*rot_matrix[2][1] + orig[2]*rot_matrix[2][2])
class Camera:
......@@ -125,19 +114,16 @@ class Camera:
xdiff = -xdiff
rot_x_angle = rot_x_factor * math.pi * ydiff / height
rot_y_angle = rot_y_factor * math.pi * xdiff / width
# calculate sinus / cosinus
rot_x_sin = math.sin(rot_x_angle)
rot_x_cos = math.cos(rot_x_angle)
rot_y_sin = math.sin(rot_y_angle)
rot_y_cos = math.cos(rot_y_angle)
# rotate around the "up" vector with the y-axis rotation
original_distance = self.view["distance"]
original_up = self.view["up"]
new_distance = rotate(original_distance, factors_y, rot_y_sin, rot_y_cos)
new_up = rotate(original_up, factors_y, rot_y_sin, rot_y_cos)
y_rot_matrix = Matrix.get_rotation_matrix_axis_angle(factors_y, rot_y_angle)
new_distance = Matrix.multiply_vector_matrix(original_distance, y_rot_matrix)
new_up = Matrix.multiply_vector_matrix(original_up, y_rot_matrix)
# rotate around the cross vector with the x-axis rotation
new_distance = rotate(new_distance, factors_x, rot_x_sin, rot_x_cos)
new_up = rotate(new_up, factors_x, rot_x_sin, rot_x_cos)
x_rot_matrix = Matrix.get_rotation_matrix_axis_angle(factors_x, rot_x_angle)
new_distance = Matrix.multiply_vector_matrix(new_distance, x_rot_matrix)
new_up = Matrix.multiply_vector_matrix(new_up, x_rot_matrix)
self.view["distance"] = new_distance
self.view["up"] = new_up
......@@ -177,23 +163,316 @@ class Camera:
return (factors_x, factors_y)
class ModelViewWindowGL:
def __init__(self, gui, settings, notify_destroy=None, accel_group=None):
# assume, that initialization will fail
self.gui = gui
self.window = self.gui.get_object("view3dwindow")
if not accel_group is None:
self.window.add_accel_group(accel_group)
self.initialized = False
self.busy = False
self.settings = settings
self.is_visible = False
# check if the 3D view is available
try:
import gtk.gtkgl
self.enabled = True
except ImportError:
show_error_dialog(self.window, "Failed to initialize the interactive 3D model view."
+ "\nPlease install 'python-gtkglext1' to enable it.")
self.enabled = False
return
self.mouse = {"start_pos": None, "button": None, "timestamp": 0}
self.notify_destroy_func = notify_destroy
self.window.connect("delete-event", self.destroy)
self.window.set_default_size(560, 400)
self._position = self.gui.get_object("ProjectWindow").get_position()
self._position = (self._position[0] + 100, self._position[1] + 100)
self.container = self.gui.get_object("view3dbox")
self.gui.get_object("Reset View").connect("clicked", self.rotate_view, VIEWS["reset"])
self.gui.get_object("Left View").connect("clicked", self.rotate_view, VIEWS["left"])
self.gui.get_object("Right View").connect("clicked", self.rotate_view, VIEWS["right"])
self.gui.get_object("Front View").connect("clicked", self.rotate_view, VIEWS["front"])
self.gui.get_object("Back View").connect("clicked", self.rotate_view, VIEWS["back"])
self.gui.get_object("Top View").connect("clicked", self.rotate_view, VIEWS["top"])
self.gui.get_object("Bottom View").connect("clicked", self.rotate_view, VIEWS["bottom"])
# key binding
self.window.connect("key-press-event", self.key_handler)
# OpenGL stuff
glconfig = gtk.gdkgl.Config(mode=gtk.gdkgl.MODE_RGB|gtk.gdkgl.MODE_DEPTH|gtk.gdkgl.MODE_DOUBLE)
self.area = gtk.gtkgl.DrawingArea(glconfig)
# first run; might also be important when doing other fancy gtk/gdk stuff
self.area.connect_after('realize', self.paint)
# called when a part of the screen is uncovered
self.area.connect('expose-event', self.paint)
# resize window
self.area.connect('configure-event', self._resize_window)
# catch mouse events
self.area.set_events(gtk.gdk.MOUSE | gtk.gdk.BUTTON_PRESS_MASK)
self.area.connect("button-press-event", self.mouse_handler)
self.area.connect('motion-notify-event', self.mouse_handler)
self.area.show()
self.container.add(self.area)
self.camera = Camera(self.settings, lambda: (self.area.allocation.width, self.area.allocation.height))
# color the dimension value according to the axes
# for "y" axis: 100% green is too bright on light background - we reduce it a bit
for color, names in (
(pango.AttrForeground(65535, 0, 0, 0, 100), ("model_dim_x_label", "model_dim_x")),
(pango.AttrForeground(0, 50000, 0, 0, 100), ("model_dim_y_label", "model_dim_y")),
(pango.AttrForeground(0, 0, 65535, 0, 100), ("model_dim_z_label", "model_dim_z"))):
attributes = pango.AttrList()
attributes.insert(color)
for name in names:
self.gui.get_object(name).set_attributes(attributes)
# show the window
self.container.show()
self.show()
def show(self):
self.is_visible = True
self.window.move(*self._position)
self.window.show()
def hide(self):
self.is_visible = False
self._position = self.window.get_position()
self.window.hide()
def key_handler(self, widget=None, event=None):
if event is None:
return
try:
keyval = getattr(event, "keyval")
get_state = getattr(event, "get_state")
except AttributeError:
return
if not (0 <= keyval <= 255):
# e.g. "shift" key
return
if chr(keyval) in ('l', 'm', 's'):
if (chr(keyval) == 'l'):
key = "view_light"
elif (chr(keyval) == 'm'):
key = "view_polygon"
elif (chr(keyval) == 's'):
key = "view_shadow"
else:
key = None
# toggle setting
self.settings.set(key, not self.settings.get(key))
# re-init gl settings
self.glsetup()
self.paint()
else:
#print "Key pressed: %s (%s)" % (chr(keyval), get_state())
pass
def check_busy(func):
def check_busy_wrapper(self, *args, **kwargs):
if not self.enabled or self.busy:
return
self.busy = True
result = func(self, *args, **kwargs)
self.busy = False
return result
return check_busy_wrapper
def gtkgl_refresh(func):
def gtkgl_refresh_wrapper(self, *args, **kwargs):
prev_mode = GL.glGetIntegerv(GL.GL_MATRIX_MODE)
GL.glMatrixMode(GL.GL_MODELVIEW)
# clear the background with the configured color
bg_col = self.settings.get("color_background")
GL.glClearColor(bg_col[0], bg_col[1], bg_col[2], 0.0)
GL.glClear(GL.GL_COLOR_BUFFER_BIT|GL.GL_DEPTH_BUFFER_BIT)
result = func(self, *args, **kwargs)
self.camera.position_camera()
self._paint_raw()
GL.glMatrixMode(prev_mode)
GL.glFlush()
self.area.get_gl_drawable().swap_buffers()
return result
return gtkgl_refresh_wrapper
def glsetup(self):
GLUT.glutInit()
if self.settings.get("view_shadow"):
GL.glShadeModel(GL.GL_FLAT)
else:
GL.glShadeModel(GL.GL_SMOOTH)
bg_col = self.settings.get("color_background")
GL.glClearColor(bg_col[0], bg_col[1], bg_col[2], 0.0)
GL.glClearDepth(1.)
GL.glEnable(GL.GL_DEPTH_TEST)
GL.glDepthFunc(GL.GL_LEQUAL)
GL.glDepthMask(GL.GL_TRUE)
GL.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST)
GL.glMatrixMode(GL.GL_MODELVIEW)
#GL.glMaterial(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, (0.1, 0.1, 0.1, 1.0))
GL.glMaterial(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR, (0.1, 0.1, 0.1, 1.0))
#GL.glMaterial(GL.GL_FRONT_AND_BACK, GL.GL_SHININESS, (0.5))
if self.settings.get("view_polygon"):
GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)
else:
GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
GL.glMatrixMode(GL.GL_MODELVIEW)
GL.glLoadIdentity()
GL.glMatrixMode(GL.GL_PROJECTION)
GL.glLoadIdentity()
GL.glViewport(0, 0, self.area.allocation.width, self.area.allocation.height)
# lightning
GL.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, (0.3, 0.3, 0.3, 3.)) # Setup The Ambient Light
GL.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, (1., 1., 1., .0)) # Setup The Diffuse Light
GL.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, (.3, .3, .3, 1.0)) # Setup The SpecularLight
GL.glEnable(GL.GL_LIGHT0)
# Enable Light One
if self.settings.get("view_light"):
GL.glEnable(GL.GL_LIGHTING)
else:
GL.glDisable(GL.GL_LIGHTING)
GL.glEnable(GL.GL_NORMALIZE)
GL.glColorMaterial(GL.GL_FRONT_AND_BACK,GL.GL_AMBIENT_AND_DIFFUSE)
#GL.glColorMaterial(GL.GL_FRONT_AND_BACK,GL.GL_SPECULAR)
#GL.glColorMaterial(GL.GL_FRONT_AND_BACK,GL.GL_EMISSION)
GL.glEnable(GL.GL_COLOR_MATERIAL)
def destroy(self, widget=None, data=None):
if self.notify_destroy_func:
self.notify_destroy_func()
# don't close the window
return True
def gtkgl_functionwrapper(function):
def gtkgl_functionwrapper_function(self, *args, **kwords):
gldrawable=self.area.get_gl_drawable()
if not gldrawable:
return
glcontext=self.area.get_gl_context()
if not gldrawable.gl_begin(glcontext):
return
if not self.initialized:
self.glsetup()
self.initialized = True
result = function(self, *args, **kwords)
gldrawable.gl_end()
return result
return gtkgl_functionwrapper_function
@check_busy
@gtkgl_functionwrapper
def mouse_handler(self, widget, event):
last_timestamp = self.mouse["timestamp"]
x, y, state = event.x, event.y, event.state
if self.mouse["button"] is None:
if (state == BUTTON_ZOOM) or (state == BUTTON_ROTATE) or (state == BUTTON_MOVE):
self.mouse["button"] = state
self.mouse["start_pos"] = [x, y]
self.area.set_events(gtk.gdk.MOUSE | gtk.gdk.BUTTON_PRESS_MASK)
else:
# not more than 25 frames per second (enough for decent visualization)
if time.time() - last_timestamp < 0.04:
return
# a button was pressed before
if state == self.mouse["button"] == BUTTON_ZOOM:
# the start button is still active: update the view
start_x, start_y = self.mouse["start_pos"]
self.mouse["start_pos"] = [x, y]
# move the mouse from lower left to top right corner for scale up
scale = 1 - 0.01 * ((x - start_x) + (start_y - y))
# do some sanity checks, scale no more than
# 1:100 on any given click+drag
if scale < 0.01:
scale = 0.01
elif scale > 100:
scale = 100
self.camera.scale_distance(scale)
self._paint_ignore_busy()
elif (state == self.mouse["button"] == BUTTON_MOVE) or (state == self.mouse["button"] == BUTTON_ROTATE):
start_x, start_y = self.mouse["start_pos"]
self.mouse["start_pos"] = [x, y]
if (state == BUTTON_MOVE):
# determine the biggest dimension (x/y/z) for moving the screen's center in relation to this value
obj_dim = []
obj_dim.append(self.settings.get("maxx") - self.settings.get("minx"))
obj_dim.append(self.settings.get("maxy") - self.settings.get("miny"))
obj_dim.append(self.settings.get("maxz") - self.settings.get("minz"))
max_dim = max(max(obj_dim[0], obj_dim[1]), obj_dim[2])
self.camera.move_camera_by_screen(x - start_x, y - start_y, max_dim)
else:
# BUTTON_ROTATE
# update the camera position according to the mouse movement
self.camera.rotate_camera_by_screen(start_x, start_y, x, y)
self._paint_ignore_busy()
else:
# button was released
self.mouse["button"] = None
self._paint_ignore_busy()
self.mouse["timestamp"] = time.time()
@check_busy
@gtkgl_functionwrapper
@gtkgl_refresh
def rotate_view(self, widget=None, view=None):
self.camera.set_view(view)
def reset_view(self):
self.rotate_view(None, None)
@check_busy
@gtkgl_functionwrapper
@gtkgl_refresh
def _resize_window(self, widget, data=None):
GL.glViewport(0, 0, self.area.allocation.width, self.area.allocation.height)
@check_busy
@gtkgl_functionwrapper
@gtkgl_refresh
def paint(self, widget=None, data=None):
# the decorators take core for redraw
pass
@gtkgl_functionwrapper
@gtkgl_refresh
def _paint_ignore_busy(self, widget=None):
pass
def _paint_raw(self, widget=None):
# draw the model
draw_complete_model_view(self.settings)
# update the dimension display
s = self.settings
dimension_bar = self.gui.get_object("view3ddimension")
if s.get("show_dimensions"):
for name, size in (
("model_dim_x", s.get("maxx") - s.get("minx")),
("model_dim_y", s.get("maxy") - s.get("miny")),
("model_dim_z", s.get("maxz") - s.get("minz"))):
self.gui.get_object(name).set_text("%.3f %s" % (size, s.get("unit")))
dimension_bar.show()
else:
dimension_bar.hide()
def keep_gl_mode(func):
def wrapper(*args, **kwargs):
def keep_gl_mode_wrapper(*args, **kwargs):
prev_mode = GL.glGetIntegerv(GL.GL_MATRIX_MODE)
func(*args, **kwargs)
result = func(*args, **kwargs)
GL.glMatrixMode(prev_mode)
return wrapper
return result
return keep_gl_mode_wrapper
def keep_matrix(func):
def wrapper(*args, **kwargs):
def keep_matrix_wrapper(*args, **kwargs):
pushed_matrix_mode = GL.glGetIntegerv(GL.GL_MATRIX_MODE)
GL.glPushMatrix()
func(*args, **kwargs)
result = func(*args, **kwargs)
final_matrix_mode = GL.glGetIntegerv(GL.GL_MATRIX_MODE)
GL.glMatrixMode(pushed_matrix_mode)
GL.glPopMatrix()
GL.glMatrixMode(final_matrix_mode)
return wrapper
return result
return keep_matrix_wrapper
@keep_matrix
def draw_string(x, y, z, p, s, scale=.01):
......
......@@ -13,14 +13,8 @@ import pycam.Geometry.utils as utils
# this requires ODE - we import it later, if necessary
#import pycam.Simulation.ODEBlocks
import pycam.Gui.OpenGLTools as ogl_tools
import pycam.Gui.ode_objects as ode_objects
import OpenGL.GL as GL
import OpenGL.GLU as GLU
import OpenGL.GLUT as GLUT
# gtk.gtkgl is imported in the constructor of "GLView" below
#import gtk.gtkgl
import pycam.Physics.ode_physics
import gtk
import pango
import ConfigParser
import math
import time
......@@ -41,10 +35,6 @@ FILTER_MODEL = ("STL models", "*.stl")
FILTER_CONFIG = ("Config files", "*.conf")
FILTER_EMC_TOOL = ("EMC tool files", "*.tbl")
BUTTON_ROTATE = gtk.gdk.BUTTON1_MASK
BUTTON_MOVE = gtk.gdk.BUTTON2_MASK
BUTTON_ZOOM = gtk.gdk.BUTTON3_MASK
COLORS = {
"color_background": (0.0, 0.0, 0.0),
"color_model": (0.5, 0.5, 1.0),
......@@ -65,13 +55,13 @@ PREFERENCES_DEFAULTS = {
"show_bounding_box": True,
"show_toolpath": True,
"show_drill_progress": False,
"color_background": COLORS["color_background"],
"color_model": COLORS["color_model"],
"color_bounding_box": COLORS["color_bounding_box"],
"color_cutter": COLORS["color_cutter"],
"color_toolpath_cut": COLORS["color_toolpath_cut"],
"color_toolpath_return": COLORS["color_toolpath_return"],
"color_material": COLORS["color_material"],
"color_background": (0.0, 0.0, 0.0),
"color_model": (0.5, 0.5, 1.0),
"color_bounding_box": (0.3, 0.3, 0.3),
"color_cutter": (1.0, 0.2, 0.2),
"color_toolpath_cut": (1.0, 0.5, 0.5),
"color_toolpath_return": (0.5, 1.0, 0.5),
"color_material": (1.0, 0.5, 0.0),
"view_light": True,
"view_shadow": True,
"view_polygon": True,
......@@ -102,299 +92,6 @@ def show_error_dialog(window, message):
warn_window.destroy()
class GLView:
def __init__(self, gui, settings, notify_destroy=None, accel_group=None):
# assume, that initialization will fail
self.gui = gui
self.window = self.gui.get_object("view3dwindow")
if not accel_group is None:
self.window.add_accel_group(accel_group)
self.initialized = False
self.busy = False
self.settings = settings
self.is_visible = False
# check if the 3D view is available
try:
import gtk.gtkgl
self.enabled = True
except ImportError:
show_error_dialog(self.window, "Failed to initialize the interactive 3D model view."
+ "\nPlease install 'python-gtkglext1' to enable it.")
self.enabled = False
return
self.mouse = {"start_pos": None, "button": None, "timestamp": 0}
self.notify_destroy_func = notify_destroy
self.window.connect("delete-event", self.destroy)
self.window.set_default_size(560, 400)
self._position = self.gui.get_object("ProjectWindow").get_position()
self._position = (self._position[0] + 100, self._position[1] + 100)
self.container = self.gui.get_object("view3dbox")
self.gui.get_object("Reset View").connect("clicked", self.rotate_view, ogl_tools.VIEWS["reset"])
self.gui.get_object("Left View").connect("clicked", self.rotate_view, ogl_tools.VIEWS["left"])
self.gui.get_object("Right View").connect("clicked", self.rotate_view, ogl_tools.VIEWS["right"])
self.gui.get_object("Front View").connect("clicked", self.rotate_view, ogl_tools.VIEWS["front"])
self.gui.get_object("Back View").connect("clicked", self.rotate_view, ogl_tools.VIEWS["back"])
self.gui.get_object("Top View").connect("clicked", self.rotate_view, ogl_tools.VIEWS["top"])
self.gui.get_object("Bottom View").connect("clicked", self.rotate_view, ogl_tools.VIEWS["bottom"])
# key binding
self.window.connect("key-press-event", self.key_handler)
# OpenGL stuff
glconfig = gtk.gdkgl.Config(mode=gtk.gdkgl.MODE_RGB|gtk.gdkgl.MODE_DEPTH|gtk.gdkgl.MODE_DOUBLE)
self.area = gtk.gtkgl.DrawingArea(glconfig)
# first run; might also be important when doing other fancy gtk/gdk stuff
self.area.connect_after('realize', self.paint)
# called when a part of the screen is uncovered
self.area.connect('expose-event', self.paint)
# resize window
self.area.connect('configure-event', self._resize_window)
# catch mouse events
self.area.set_events(gtk.gdk.MOUSE | gtk.gdk.BUTTON_PRESS_MASK)
self.area.connect("button-press-event", self.mouse_handler)
self.area.connect('motion-notify-event', self.mouse_handler)
self.area.show()
self.container.add(self.area)
self.camera = ogl_tools.Camera(self.settings, lambda: (self.area.allocation.width, self.area.allocation.height))
# color the dimension value according to the axes
# for "y" axis: 100% green is too bright on light background - we reduce it a bit
for color, names in (
(pango.AttrForeground(65535, 0, 0, 0, 100), ("model_dim_x_label", "model_dim_x")),
(pango.AttrForeground(0, 50000, 0, 0, 100), ("model_dim_y_label", "model_dim_y")),
(pango.AttrForeground(0, 0, 65535, 0, 100), ("model_dim_z_label", "model_dim_z"))):
attributes = pango.AttrList()
attributes.insert(color)
for name in names:
self.gui.get_object(name).set_attributes(attributes)
# show the window
self.container.show()
self.show()
def show(self):
self.is_visible = True
self.window.move(*self._position)
self.window.show()
def hide(self):
self.is_visible = False
self._position = self.window.get_position()
self.window.hide()
def key_handler(self, widget=None, event=None):
if event is None:
return
try:
keyval = getattr(event, "keyval")
get_state = getattr(event, "get_state")
except AttributeError:
return
if not (0 <= keyval <= 255):
# e.g. "shift" key
return
if chr(keyval) in ('l', 'm', 's'):
if (chr(keyval) == 'l'):
key = "view_light"
elif (chr(keyval) == 'm'):
key = "view_polygon"
elif (chr(keyval) == 's'):
key = "view_shadow"
else:
key = None
# toggle setting
self.settings.set(key, not self.settings.get(key))
# re-init gl settings
self.glsetup()
self.paint()
else:
#print "Key pressed: %s (%s)" % (chr(keyval), get_state())
pass
def check_busy(func):
def busy_wrapper(self, *args, **kwargs):
if not self.enabled or self.busy:
return
self.busy = True
func(self, *args, **kwargs)
self.busy = False
return busy_wrapper
def gtkgl_refresh(func):
def refresh_wrapper(self, *args, **kwargs):
prev_mode = GL.glGetIntegerv(GL.GL_MATRIX_MODE)
GL.glMatrixMode(GL.GL_MODELVIEW)
# clear the background with the configured color
bg_col = self.settings.get("color_background")
GL.glClearColor(bg_col[0], bg_col[1], bg_col[2], 0.0)
GL.glClear(GL.GL_COLOR_BUFFER_BIT|GL.GL_DEPTH_BUFFER_BIT)
result = func(self, *args, **kwargs)
self.camera.position_camera()
self._paint_raw()
GL.glMatrixMode(prev_mode)
GL.glFlush()
self.area.get_gl_drawable().swap_buffers()
return result
return refresh_wrapper
def glsetup(self):
GLUT.glutInit()
if self.settings.get("view_shadow"):
GL.glShadeModel(GL.GL_FLAT)
else:
GL.glShadeModel(GL.GL_SMOOTH)
bg_col = self.settings.get("color_background")
GL.glClearColor(bg_col[0], bg_col[1], bg_col[2], 0.0)
GL.glClearDepth(1.)
GL.glEnable(GL.GL_DEPTH_TEST)
GL.glDepthFunc(GL.GL_LEQUAL)
GL.glDepthMask(GL.GL_TRUE)
GL.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST)
GL.glMatrixMode(GL.GL_MODELVIEW)
#GL.glMaterial(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, (0.1, 0.1, 0.1, 1.0))
GL.glMaterial(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR, (0.1, 0.1, 0.1, 1.0))
#GL.glMaterial(GL.GL_FRONT_AND_BACK, GL.GL_SHININESS, (0.5))
if self.settings.get("view_polygon"):
GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)
else:
GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
GL.glMatrixMode(GL.GL_MODELVIEW)
GL.glLoadIdentity()
GL.glMatrixMode(GL.GL_PROJECTION)
GL.glLoadIdentity()
GL.glViewport(0, 0, self.area.allocation.width, self.area.allocation.height)
# lightning
GL.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, (0.3, 0.3, 0.3, 3.)) # Setup The Ambient Light
GL.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, (1., 1., 1., .0)) # Setup The Diffuse Light
GL.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, (.3, .3, .3, 1.0)) # Setup The SpecularLight
GL.glEnable(GL.GL_LIGHT0)
# Enable Light One
if self.settings.get("view_light"):
GL.glEnable(GL.GL_LIGHTING)
else:
GL.glDisable(GL.GL_LIGHTING)
GL.glEnable(GL.GL_NORMALIZE)
GL.glColorMaterial(GL.GL_FRONT_AND_BACK,GL.GL_AMBIENT_AND_DIFFUSE)
#GL.glColorMaterial(GL.GL_FRONT_AND_BACK,GL.GL_SPECULAR)
#GL.glColorMaterial(GL.GL_FRONT_AND_BACK,GL.GL_EMISSION)
GL.glEnable(GL.GL_COLOR_MATERIAL)
def destroy(self, widget=None, data=None):
if self.notify_destroy_func:
self.notify_destroy_func()
# don't close the window
return True
def gtkgl_functionwrapper(function):
def decorated(self, *args, **kwords):
gldrawable=self.area.get_gl_drawable()
if not gldrawable:
return
glcontext=self.area.get_gl_context()
if not gldrawable.gl_begin(glcontext):
return
if not self.initialized:
self.glsetup()
self.initialized = True
result = function(self, *args, **kwords)
gldrawable.gl_end()
return result
return decorated # TODO: make this a well behaved decorator (keeping name, docstring etc)
def keyboard_handler(self, widget, event):
print "KEY:", event
@check_busy
@gtkgl_functionwrapper
def mouse_handler(self, widget, event):
last_timestamp = self.mouse["timestamp"]
x, y, state = event.x, event.y, event.state
if self.mouse["button"] is None:
if (state == BUTTON_ZOOM) or (state == BUTTON_ROTATE) or (state == BUTTON_MOVE):
self.mouse["button"] = state
self.mouse["start_pos"] = [x, y]
self.area.set_events(gtk.gdk.MOUSE | gtk.gdk.BUTTON_PRESS_MASK)
else:
# not more than 25 frames per second (enough for decent visualization)
if time.time() - last_timestamp < 0.04:
return
# a button was pressed before
if state == self.mouse["button"] == BUTTON_ZOOM:
# the start button is still active: update the view
start_x, start_y = self.mouse["start_pos"]
self.mouse["start_pos"] = [x, y]
# move the mouse from lower left to top right corner for scale up
scale = 1 - 0.01 * ((x - start_x) + (start_y - y))
# do some sanity checks, scale no more than
# 1:100 on any given click+drag
if scale < 0.01:
scale = 0.01
elif scale > 100:
scale = 100
self.camera.scale_distance(scale)
self._paint_ignore_busy()
elif (state == self.mouse["button"] == BUTTON_MOVE) or (state == self.mouse["button"] == BUTTON_ROTATE):
start_x, start_y = self.mouse["start_pos"]
self.mouse["start_pos"] = [x, y]
if (state == BUTTON_MOVE):
# determine the biggest dimension (x/y/z) for moving the screen's center in relation to this value
obj_dim = []
obj_dim.append(self.settings.get("maxx") - self.settings.get("minx"))
obj_dim.append(self.settings.get("maxy") - self.settings.get("miny"))
obj_dim.append(self.settings.get("maxz") - self.settings.get("minz"))
max_dim = max(max(obj_dim[0], obj_dim[1]), obj_dim[2])
self.camera.move_camera_by_screen(x - start_x, y - start_y, max_dim)
else:
# BUTTON_ROTATE
# update the camera position according to the mouse movement
self.camera.rotate_camera_by_screen(start_x, start_y, x, y)
self._paint_ignore_busy()
else:
# button was released
self.mouse["button"] = None
self._paint_ignore_busy()
self.mouse["timestamp"] = time.time()
@check_busy
@gtkgl_functionwrapper
@gtkgl_refresh
def rotate_view(self, widget=None, view=None):
self.camera.set_view(view)
def reset_view(self):
self.rotate_view(None, None)
@check_busy
@gtkgl_functionwrapper
@gtkgl_refresh
def _resize_window(self, widget, data=None):
GL.glViewport(0, 0, self.area.allocation.width, self.area.allocation.height)
@check_busy
@gtkgl_functionwrapper
@gtkgl_refresh
def paint(self, widget=None, data=None):
# the decorators take core for redraw
pass
@gtkgl_functionwrapper
@gtkgl_refresh
def _paint_ignore_busy(self, widget=None):
pass
def _paint_raw(self, widget=None):
# draw the model
ogl_tools.draw_complete_model_view(self.settings)
# update the dimension display
s = self.settings
dimension_bar = self.gui.get_object("view3ddimension")
if s.get("show_dimensions"):
for name, size in (
("model_dim_x", s.get("maxx") - s.get("minx")),
("model_dim_y", s.get("maxy") - s.get("miny")),
("model_dim_z", s.get("maxz") - s.get("minz"))):
self.gui.get_object(name).set_text("%.3f %s" % (size, s.get("unit")))
dimension_bar.show()
else:
dimension_bar.hide()
class ProjectGui:
BOUNDARY_MODES = {
......@@ -551,7 +248,7 @@ class ProjectGui:
obj.connect("color-set", self.update_view)
# set the availability of ODE
enable_ode_control = self.gui.get_object("SettingEnableODE")
if ode_objects.is_ode_available():
if pycam.Physics.ode_physics.is_ode_available():
self.settings.add_item("enable_ode", enable_ode_control.get_active, enable_ode_control.set_active)
else:
enable_ode_control.set_sensitive(False)
......@@ -716,7 +413,7 @@ class ProjectGui:
def get_physics(self, cutter):
if self.settings.get("enable_ode"):
self._physics_cache = ode_objects.generate_physics(self.model,
self._physics_cache = pycam.Physics.ode_physics.generate_physics(self.model,
cutter, self._physics_cache)
else:
self._physics_cache = None
......@@ -963,7 +660,7 @@ class ProjectGui:
elif new_state:
if self.view3d is None:
# do the gl initialization
self.view3d = GLView(self.gui, self.settings,
self.view3d = ogl_tools.ModelViewWindowGL(self.gui, self.settings,
notify_destroy=self.toggle_3d_view,
accel_group=self._accel_group)
if self.model and self.view3d.enabled:
......@@ -1635,7 +1332,7 @@ class ProjectGui:
self.gui.get_object("toolpath_up").set_sensitive((not new_index is None) and (new_index > 0))
self.gui.get_object("toolpath_delete").set_sensitive(not new_index is None)
self.gui.get_object("toolpath_down").set_sensitive((not new_index is None) and (new_index + 1 < len(self.toolpath)))
self.gui.get_object("toolpath_simulate").set_sensitive((not new_index is None) and ode_objects.is_ode_available())
self.gui.get_object("toolpath_simulate").set_sensitive((not new_index is None) and pycam.Physics.ode_physics.is_ode_available())
@gui_activity_guard
def save_task_settings_file(self, widget=None, filename=None):
......
# Tkinter is used for "EmergencyDialog" below - but we will try to import it carefully
#import Tkinter
# "ode" is imported later, if required
#import ode_objects
import random
import sys
import os
......
#!/usr/bin/python
from pycam.Gui.ode_objects import override_ode_availability
from pycam.Physics.ode_physics import override_ode_availability
import pycam.Gui.common as GuiCommon
from optparse import OptionParser
import sys
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment