jpcad/SketchWidget.py
2011-04-03 12:14:16 -04:00

266 lines
9.4 KiB
Python

import math
import gtk
import gtk.gtkgl
from OpenGL.GL import *
from shapes import *
class SketchWidget:
def __init__(self, sketch):
self.sketch = sketch
self.view_center = (0, 0)
self.view_width = 4.0
self.size = (1, 1)
self.panning = False
self.drawingLine = None
self.drawingCircle = None
# Configuration parameters
self.line_width = 0.8
self.axis_width = 1.8
self.axis_length = 50
self.zoom_factor = 1.2
self.background_color = (0.6, 0.9, 1.0, 1.0)
self.line_color = (0.1, 0.4, 1.0, 1.0)
self.axis_color = (1.0, 0.0, 0.0, 1.0)
try:
# try double-buffered
self.glconfig = gtk.gdkgl.Config(mode = (gtk.gdkgl.MODE_RGB |
gtk.gdkgl.MODE_DOUBLE |
gtk.gdkgl.MODE_DEPTH))
except gtk.gdkgl.NoMatches:
# try single-buffered
self.glconfig = gtk.gdkgl.Config(mode = (gtk.gdkgl.MODE_RGB |
gtk.gdkgl.MODE_DEPTH))
self.widget = gtk.gtkgl.DrawingArea(self.glconfig)
self.widget.connect_after('realize', self.init)
self.widget.connect('configure_event', self.reshape)
self.widget.connect('expose_event', self.draw)
self.widget.connect('button-press-event', self.button_press_event)
self.widget.connect('button-release-event', self.button_release_event)
self.widget.connect('motion-notify-event', self.motion_event)
self.widget.connect('scroll-event', self.scroll_event)
self.widget.add_events(gtk.gdk.POINTER_MOTION_MASK
| gtk.gdk.BUTTON_PRESS_MASK
| gtk.gdk.BUTTON_RELEASE_MASK
| gtk.gdk.SCROLL_MASK)
def init(self, glarea):
# get GLContext and GLDrawable
glcontext = glarea.get_gl_context()
gldrawable = glarea.get_gl_drawable()
# GL calls
if not gldrawable.gl_begin(glcontext): return
# glEnable(GL_BLEND)
# glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
# glEnable(GL_POLYGON_SMOOTH)
# glEnable(GL_LINE_SMOOTH)
glClearColor(*self.background_color)
gldrawable.gl_end()
def reshape(self, glarea, event):
# get GLContext and GLDrawable
glcontext = glarea.get_gl_context()
gldrawable = glarea.get_gl_drawable()
# GL calls
if not gldrawable.gl_begin(glcontext): return
x, y, width, height = glarea.get_allocation()
self.size = (float(width), float(height))
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, width, 0, height, 1.0, -1.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
gldrawable.gl_end()
return True
def draw(self, glarea, event):
# get GLContext and GLDrawable
glcontext = glarea.get_gl_context()
gldrawable = glarea.get_gl_drawable()
# GL calls
if not gldrawable.gl_begin(glcontext): return
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
self.drawAxes()
glColor(*self.line_color)
for shape in self.sketch:
if isinstance(shape, Line):
self.drawLine(shape, self.line_width)
elif isinstance(shape, Circle):
self.drawCircle(shape, self.line_width)
if self.drawingLine is not None:
self.drawLine(self.drawingLine, self.line_width)
if gldrawable.is_double_buffered():
gldrawable.swap_buffers()
else:
glFlush()
gldrawable.gl_end()
return True
def ptToScreenPt(self, pt):
return ((pt[0] - self.view_center[0]) / self.view_width * self.size[0]
+ self.size[0] / 2,
(pt[1] - self.view_center[1]) / self.view_width * self.size[0]
+ self.size[1] / 2)
def distToScreenDist(self, dist):
return dist / self.view_width * self.size[0]
def screenPtToPt(self, pt):
return ((pt[0] - self.size[0] / 2) / self.size[0] * self.view_width
+ self.view_center[0],
(pt[1] - self.size[1] / 2) / self.size[0] * self.view_width
+ self.view_center[1])
def screenDistToDist(self, dist):
return dist * self.size[0] / self.view_width
def drawLine(self, shape, size):
pt0 = self.ptToScreenPt(shape.getPt(0))
pt1 = self.ptToScreenPt(shape.getPt(1))
self.drawFilledLine(pt0[0], pt0[1], pt1[0], pt1[1], size)
def drawCircle(self, shape, size):
center = self.ptToScreenPt(shape.getCenter())
rad = self.distToScreenDist(shape.getRadius())
if rad < 30:
steps = 12
elif rad < 50:
steps = 16
elif rad < 100:
steps = 24
elif rad < 200:
steps = 36
elif rad < 400:
steps = 48
else:
steps = 64
step = 2 * math.pi / steps
for i in range(steps + 1):
angle = i * step
next_angle = (i + 1) * step
x0 = center[0] + rad * math.cos(angle)
y0 = center[1] + rad * math.sin(angle)
x1 = center[0] + rad * math.cos(next_angle)
y1 = center[1] + rad * math.sin(next_angle)
self.drawFilledLineUncapped(x0, y0, x1, y1, size)
self.drawFilledCircle(x0, y0, size, 8)
def drawFilledLine(self, x0, y0, x1, y1, size):
self.drawFilledLineUncapped(x0, y0, x1, y1, size)
self.drawFilledCircle(x0, y0, size, 16)
self.drawFilledCircle(x1, y1, size, 16)
def drawFilledLineUncapped(self, x0, y0, x1, y1, size):
glBegin(GL_QUADS)
angle = math.atan2(y1 - y0, x1 - x0)
ninety = math.pi / 2
x_left = size * math.cos(angle + ninety)
y_left = size * math.sin(angle + ninety)
x_right = size * math.cos(angle - ninety)
y_right = size * math.sin(angle - ninety)
glVertex(x0 + x_left, y0 + y_left)
glVertex(x0 + x_right, y0 + y_right)
glVertex(x1 + x_right, y1 + y_right)
glVertex(x1 + x_left, y1 + y_left)
glEnd()
def drawFilledCircle(self, x, y, radius, steps):
glBegin(GL_TRIANGLE_FAN)
glVertex(x, y)
for i in range(steps + 1):
angle = i * 2 * math.pi / steps
glVertex(x + radius * math.cos(angle), y + radius * math.sin(angle))
glEnd()
def drawAxes(self):
glPushAttrib(GL_CURRENT_BIT)
glColor(*self.axis_color)
cx, cy = self.ptToScreenPt((0, 0))
self.drawFilledLine(cx - self.axis_length, cy,
cx + self.axis_length, cy, self.axis_width)
self.drawFilledLine(cx, cy - self.axis_length,
cx, cy + self.axis_length, self.axis_width)
glPopAttrib()
def button_press_event(self, widget, event, data = None):
if event.button == 1:
if self.drawingLine is not None:
self.sketch.shapes.append(self.drawingLine)
start = self.screenPtToPt((event.x, self.size[1] - event.y))
self.drawingLine = Line(start[0], start[1], start[0], start[1])
elif event.button == 2:
self.panning = True
self.panning_start = (event.x, self.size[1] - event.y)
elif event.button == 3:
if self.drawingLine is not None:
# cancel line currently being drawn
self.drawingLine = None
self.queue_redraw()
def button_release_event(self, widget, event, data = None):
if event.button == 2:
self.panning = False
def motion_event(self, widget, event, data = None):
if self.panning:
start_pt = self.screenPtToPt(self.panning_start)
this_pt = self.screenPtToPt((event.x, self.size[1] - event.y))
self.view_center = (self.view_center[0] - this_pt[0] + start_pt[0],
self.view_center[1] - this_pt[1] + start_pt[1])
self.panning_start = (event.x, self.size[1] - event.y)
self.queue_redraw()
elif self.drawingLine is not None:
this_pt = self.screenPtToPt((event.x, self.size[1] - event.y))
self.drawingLine.setPt(1, this_pt)
self.queue_redraw()
def scroll_event(self, widget, event, data = None):
if event.direction == gtk.gdk.SCROLL_UP:
zoom_pt = self.screenPtToPt((event.x,
self.size[1] - event.y))
off_x = zoom_pt[0] - self.view_center[0]
off_y = zoom_pt[1] - self.view_center[1]
self.view_center = (zoom_pt[0] - off_x / self.zoom_factor,
zoom_pt[1] - off_y / self.zoom_factor)
self.view_width /= self.zoom_factor
self.queue_redraw()
elif event.direction == gtk.gdk.SCROLL_DOWN:
zoom_pt = self.screenPtToPt((event.x,
self.size[1] - event.y))
off_x = zoom_pt[0] - self.view_center[0]
off_y = zoom_pt[1] - self.view_center[1]
self.view_center = (zoom_pt[0] - off_x * self.zoom_factor,
zoom_pt[1] - off_y * self.zoom_factor)
self.view_width *= self.zoom_factor
self.queue_redraw()
def queue_redraw(self):
self.widget.queue_draw_area(0, 0,
int(self.size[0]), int(self.size[1]))