Source code for lib_openmolar.client.qt4.widgets.chart_widgets.teeth

#! /usr/bin/env python
# -*- coding: utf-8 -*-

###############################################################################
##                                                                           ##
##  Copyright 2010, Neil Wallace <rowinggolfer@googlemail.com>               ##
##                                                                           ##
##  This program is free software: you can redistribute it and/or modify     ##
##  it under the terms of the GNU General Public License as published by     ##
##  the Free Software Foundation, either version 3 of the License, or        ##
##  (at your option) any later version.                                      ##
##                                                                           ##
##  This program is distributed in the hope that it will be useful,          ##
##  but WITHOUT ANY WARRANTY; without even the implied warranty of           ##
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            ##
##  GNU General Public License for more details.                             ##
##                                                                           ##
##  You should have received a copy of the GNU General Public License        ##
##  along with this program.  If not, see <http://www.gnu.org/licenses/>.    ##
##                                                                           ##
###############################################################################

from __future__ import division

import logging
import re

from PyQt4 import QtGui, QtCore

from lib_openmolar.client.classes import Tooth
from lib_openmolar.client.qt4.colours import colours


[docs]class ChartTooth(Tooth): ''' custom class which acts as a view to the data stored in :doc:`ChartDataModel` for a tooth '''
[docs] def __init__(self, tooth_id, model): ''' :param: int (should conform to :doc:`../../misc/tooth_notation`) :param: :doc:`ChartDataModel` ''' Tooth.__init__(self, tooth_id) self.data_model = model self.rect = QtCore.QRectF() self.under_mouse = False self.is_selected = False self.is_root = False self.is_present = True self.fill_shapes = [] self.fill_shapes_current = True self.crowns = [] self.ignore = False self.root = None
def __repr__(self): return "tooth om_id=%d, shortname='%s' longname='%s' root='%s'"% ( self.tooth_id, self.short_name, self.long_name, self.is_root) def __cmp__(self, other): try: return cmp( "%s%s"%(self.tooth_id, self.is_root), "%s%s"%(other.tooth_id, other.is_root)) except AttributeError: return -1 @property
[docs] def has_properties(self): return self.data_model.has_properties(self.tooth_id)
@property
[docs] def properties(self): return self.data_model.get_properties(self.tooth_id)
@property
[docs] def restorations(self): return self.data_model.get_restorations(self.tooth_id)
[docs] def set_ignore(self, boolean): self.ignore = boolean
[docs] def set_selected(self, boolean): self.is_selected = boolean
[docs] def toggle_selection(self): self.set_selected(not self.is_selected)
[docs] def set_is_present(self, boolean): self.is_present = boolean
[docs] def toggle_is_present(self): self.set_is_present(not self.is_present) if self.root: self.root.toggle_is_present()
[docs] def add_property(self, prop): ''' convience method to add data to the underlying model ''' self.data_model.add_property(prop) self.data_model.endResetModel()
[docs] def set_rect(self, rect): ''' update the rectangle of the tooth ''' self.rect = rect if self.is_deciduous: h = rect.height()*.08 w = rect.width()*.08 self.rect = self.rect.adjusted(w,h,-w,-h) self.set_graphics_points() self.fill_shapes_current = False
[docs] def select_rect(self, include_root = False): if not self.root or not include_root: rect = self.rect elif self.is_upper: rect = QtCore.QRectF(self.root.rect.topLeft(), self.rect.bottomRight()) else: rect = QtCore.QRectF(self.rect.topLeft(), self.root.rect.bottomRight()) return rect.adjusted(0 ,0, -1, -1)
@property
[docs] def properties_as_string(self): prop_str = u"" for prop in self.properties: prop_str += prop.text return prop_str
@property
[docs] def ref_next(self): ''' returns the next tooth in a forward direction (eg if return is pressed) ''' if self.tooth_id == 32: return 1 if self.tooth_id == 84: return 65 return self.tooth_id + 1
@property
[docs] def ref_prev(self): ''' returns the next tooth in a backward direction ''' if self.tooth_id == 1: return 32 if self.tooth_id == 65: return 84 return self.tooth_id - 1
@property
[docs] def ref_left(self): ''' returns the appropriate ref for the left arrow_key ''' if self.is_upper: return self.ref_prev else: return self.ref_next
@property
[docs] def ref_right(self): ''' returns the appropriate ref for the left arrow_key ''' if self.is_upper: return self.ref_next else: return self.ref_prev
@property
[docs] def row_down(self): ''' returns the appropriate tooth in a downwards direction ''' found = False for row in SETTINGS.visible_chart_rows: if self.tooth_id in SETTINGS.TOOTH_GRID[row]: index = SETTINGS.TOOTH_GRID[row].index(self.tooth_id) found = True break if not found: print "not found!" return if row == SETTINGS.visible_chart_rows[-1]: return self.tooth_id i = SETTINGS.visible_chart_rows.index(row) row_down = SETTINGS.visible_chart_rows[i+1] new_val = SETTINGS.TOOTH_GRID[row_down][index] if new_val == 0: return self.tooth_id return new_val
@property
[docs] def row_up(self): ''' returns the appropriate tooth in a downwards direction ''' found = False for row in reversed(SETTINGS.visible_chart_rows): if self.tooth_id in SETTINGS.TOOTH_GRID[row]: index = SETTINGS.TOOTH_GRID[row].index(self.tooth_id) found = True break if not found: print "not found!" return if row == SETTINGS.visible_chart_rows[0]: return self.tooth_id i = SETTINGS.visible_chart_rows.index(row) row_up = SETTINGS.visible_chart_rows[i-1] new_val = SETTINGS.TOOTH_GRID[row_up][index] if new_val == 0: return self.tooth_id return new_val
@property
[docs] def line_edit_text(self): return self.properties_as_string
[docs] def set_graphics_points(self): toothdimen = self.rect.width() x = self.rect.topLeft().x() y = self.rect.topLeft().y() if self.is_backtooth: self.ax = x + toothdimen * 0.05 self.bx = x + toothdimen * 0.15 self.cx = x + toothdimen * 0.2 self.dx = x + toothdimen * 0.35 self.ex = x + toothdimen * 0.5 self.fx = x + toothdimen * 0.65 self.gx = x + toothdimen * 0.8 self.hx = x + toothdimen * 0.85 self.ix = x + toothdimen * 0.95 toothdimen = self.rect.height() self.ay = y + toothdimen * 0.05 self.by = y + toothdimen * 0.15 self.cy = y + toothdimen * 0.2 self.dy = y + toothdimen * 0.35 self.ey = y + toothdimen * 0.5 self.fy = y + toothdimen * 0.65 self.gy = y + toothdimen * 0.8 self.hy = y + toothdimen * 0.85 self.iy = y + toothdimen * 0.95 else: #--front tooth - different patterns self.ax = x + toothdimen * 0.05 self.bx = x + toothdimen * 0.15 self.cx = x + toothdimen * 0.2 self.dx = x + toothdimen * 0.3 self.ex = x + toothdimen * 0.5 self.fx = x + toothdimen * 0.7 self.gx = x + toothdimen * 0.8 self.hx = x + toothdimen * 0.85 self.ix = x + toothdimen * 0.95 toothdimen = self.rect.height() self.ay = y + toothdimen * 0.05 self.by = y + toothdimen * 0.15 self.cy = y + toothdimen * 0.2 self.dy = y + toothdimen * 0.3 self.ey = y + toothdimen * 0.5 self.fy = y + toothdimen * 0.7 self.gy = y + toothdimen * 0.8 self.hy = y + toothdimen * 0.85 self.iy = y + toothdimen * 0.95 #--the occlusal surface (for backteeth) #--or incisal edge for front teeth.. #-- is given a width here. #-- irw = inner rectangle width irw = self.rect.width() * 0.25 if self.is_backtooth: irh = self.rect.height() * 0.25 else: irh = self.rect.height() * 0.45 self.innerRect = self.rect.adjusted(irw, irh, -irw, -irh)
[docs] def init_restoration_shapes(self): # check to see whether this LONG procedure is necessary if self.fill_shapes_current: pass self.fill_shapes = [] self.crowns = [] for prop in self.restorations: if prop.type == prop.CROWN: self.crowns.append(prop) continue surfaces, material = prop.surfaces_to_draw, prop.material brush, shape = None, None #--set filling color if material in ("CO", "FS"): brush = colours.COMPOSITE elif material == "GL": brush = colours.GLASS elif material == "GO": brush = colours.GOLD elif material == "PO": brush = colours.PORCELAIN elif material == "AM": brush = colours.AMALGAM else: print "unhanded material colour", material if self.is_backtooth: if prop.is_fissure_sealant: shape = QtGui.QPolygon([self.dx-2, self.ey-2, self.fx+2, self.ey-2, self.fx+2, self.ey+2, self.dx-2, self.ey+2]) elif re.match("[MODBL]{5}", surfaces): shape = QtGui.QPolygon([self.ax, self.by, self.cx, self.dy, self.dx, self.dy, self.dx, self.by, self.fx, self.by, self.fx, self.dy, self.gx, self.dy, self.ix, self.by, self.ix, self.hy, self.gx, self.fy, self.fx, self.fy, self.fx, self.hy, self.dx, self.hy, self.dx, self.fy, self.cx, self.fy, self.ax, self.hy]) elif re.match("[MODB]{4}", surfaces): shape = QtGui.QPolygon([self.ax, self.by, self.dx, self.dy, self.dx, self.by, self.fx, self.by, self.fx, self.dy, self.ix, self.by, self.ix, self.hy, self.fx, self.fy, self.dx, self.fy, self.ax, self.hy]) elif re.match("[MODL]{4}", surfaces): shape = QtGui.QPolygon([self.ax, self.by, self.dx, self.dy, self.fx, self.dy, self.ix, self.by, self.ix, self.hy, self.fx, self.fy, self.fx, self.hy, self.dx, self.hy, self.dx, self.fy, self.ax, self.hy]) elif re.match("[MOD]{3}", surfaces): shape = QtGui.QPolygon([self.ax, self.by, self.dx, self.dy, self.fx, self.dy, self.ix, self.by, self.ix, self.hy, self.fx, self.fy, self.dx, self.fy, self.ax, self.hy]) elif re.match("[MOB]{3}", surfaces): shape = QtGui.QPolygon([self.dx, self.dy, self.ex, self.dy, self.ex, self.by, self.fx, self.by, self.fx, self.dy, self.gx, self.dy, self.ix, self.cy, self.ix, self.gy, self.gx, self.fy, self.dx, self.fy]) elif re.match("[MOL]{3}", surfaces): shape = QtGui.QPolygon([self.dx, self.dy, self.gx, self.dy, self.ix, self.cy, self.ix, self.gy, self.gx, self.fy, self.fx, self.fy, self.fx, self.hy, self.ex, self.hy, self.ex, self.fy, self.dx, self.fy]) elif re.match("[DOB]{3}", surfaces): shape = QtGui.QPolygon([self.ax, self.cy, self.cx, self.dy, self.dx, self.dy, self.dx, self.by, self.ex, self.by, self.ex, self.dy, self.fx, self.dy, self.fx, self.fy, self.cx, self.fy, self.ax, self.gy]) elif re.match("[DOL]{3}", surfaces): shape = QtGui.QPolygon([self.ax, self.cy, self.cx, self.dy, self.fx, self.dy, self.fx, self.fy, self.ex, self.fy, self.ex, self.hy, self.dx, self.hy, self.dx, self.fy, self.cx, self.fy, self.ax, self.gy]) elif re.match("[MBD]{3}", surfaces): shape = QtGui.QPolygon([self.ax, self.by, self.dx, self.ay, self.fx, self.ay, self.ix, self.by, self.ix, self.ey, self.hx, self.ey, self.hx, self.cy, self.bx, self.cy, self.bx, self.ey, self.ax, self.ey]) elif re.match("[MLD]{3}", surfaces): shape = QtGui.QPolygon([self.ax, self.ey, self.bx, self.ey, self.bx, self.hy, self.hx, self.hy, self.hx, self.ey, self.ix, self.ey, self.ix, self.gy, self.gx, self.iy, self.bx, self.iy, self.ax, self.gy]) elif re.match("[OB]{2}", surfaces): shape = QtGui.QPolygon([self.cx, self.ay, self.gx, self.ay, self.fx, self.cy, self.fx, self.fy, self.dx, self.fy, self.dx, self.cy]) elif re.match("[OL]{2}", surfaces): shape = QtGui.QPolygon([self.dx, self.dy, self.fx, self.dy, self.fx, self.gy, self.gx, self.iy, self.cx, self.iy, self.dx, self.gy]) elif re.match("[MB]{2}", surfaces): shape = QtGui.QPolygon([self.dx, self.ay, self.fx, self.ay, self.ix, self.by, self.ix, self.ey, self.hx, self.ey, self.hx, self.dy, self.fx, self.cy, self.dx, self.cy, self.bx, self.by]) elif re.match("[ML]{2}", surfaces): shape = QtGui.QPolygon([self.dx, self.iy, self.fx, self.iy, self.ix, self.hy, self.ix, self.ey, self.hx, self.ey, self.hx, self.fy, self.fx, self.gy, self.dx, self.gy, self.bx, self.hy]) elif re.match("[DB]{2}", surfaces): shape = QtGui.QPolygon([self.fx, self.ay, self.dx, self.ay, self.ax, self.by, self.ax, self.ey, self.bx, self.ey, self.bx, self.dy, self.dx, self.cy, self.fx, self.cy, self.hx, self.by]) elif re.match("[DL]{2}", surfaces): shape = QtGui.QPolygon([self.fx, self.iy, self.dx, self.iy, self.ax, self.hy, self.ax, self.ey, self.bx, self.ey, self.bx, self.fy, self.dx, self.gy, self.fx, self.gy, self.hx, self.hy]) elif re.match("[MO]{2}", surfaces): shape = QtGui.QPolygon([self.dx, self.dy, self.gx, self.dy, self.ix, self.cy, self.ix, self.gy, self.gx, self.fy, self.dx, self.fy]) elif re.match("[DO]{2}", surfaces): shape = QtGui.QPolygon([self.ax, self.cy, self.cx, self.dy, self.fx, self.dy, self.fx, self.fy, self.cx, self.fy, self.ax, self.gy]) elif "O" in surfaces: shape = QtGui.QPolygon([self.dx, self.dy, self.fx, self.dy, self.fx, self.fy, self.dx, self.fy]) elif "M" in surfaces: shape = QtGui.QPolygon([self.gx, self.dy, self.ix, self.by, self.ix, self.hy, self.gx, self.fy]) elif "D" in surfaces: shape = QtGui.QPolygon([self.ax, self.by, self.cx, self.dy, self.cx, self.fy, self.ax, self.hy]) elif "L" in surfaces: shape = QtGui.QPolygon([self.bx, self.iy, self.dx, self.gy, self.fx, self.gy, self.hx, self.iy]) elif "B" in surfaces: shape = QtGui.QPolygon([self.bx, self.ay, self.hx, self.ay, self.fx, self.cy, self.dx, self.cy]) else: #front tooth if re.match("[MBD]{3}", surfaces): shape = QtGui.QPolygon([self.ax, self.by, self.dx, self.ay, self.fx, self.ay, self.ix, self.by, self.ix, self.ey, self.hx, self.ey, self.hx, self.cy, self.bx, self.cy, self.bx, self.ey, self.ax, self.ey]) elif re.match("[MLD]{3}", surfaces): shape = QtGui.QPolygon([self.ax, self.ey, self.bx, self.ey, self.bx, self.hy, self.hx, self.hy, self.hx, self.ey, self.ix, self.ey, self.ix, self.gy, self.gx, self.iy, self.bx, self.iy, self.ax, self.gy]) elif re.match("[OB]{2}", surfaces): shape = QtGui.QPolygon([self.cx, self.ay, self.gx, self.ay, self.fx, self.cy, self.fx, self.fy, self.dx, self.fy, self.dx, self.cy]) elif re.match("[OL]{2}", surfaces): shape = QtGui.QPolygon([self.dx, self.dy, self.fx, self.dy, self.fx, self.gy, self.gx, self.iy, self.cx, self.iy, self.dx, self.gy]) elif re.match("[MB]{2}", surfaces): shape = QtGui.QPolygon([self.dx, self.ay, self.fx, self.ay, self.ix, self.by, self.ix, self.ey, self.hx, self.ey, self.hx, self.dy, self.fx, self.cy, self.dx, self.cy, self.bx, self.by]) elif re.match("[ML]{2}", surfaces): shape = QtGui.QPolygon([self.dx, self.iy, self.fx, self.iy, self.ix, self.hy, self.ix, self.ey, self.hx, self.ey, self.hx, self.fy, self.fx, self.gy, self.dx, self.gy, self.bx, self.hy]) elif re.match("[DB]{2}", surfaces): shape = QtGui.QPolygon([self.fx, self.ay, self.dx, self.ay, self.ax, self.by, self.ax, self.ey, self.bx, self.ey, self.bx, self.dy, self.dx, self.cy, self.fx, self.cy, self.hx, self.by]) elif re.match("[DL]{2}", surfaces): shape = QtGui.QPolygon([self.fx, self.iy, self.dx, self.iy, self.ax, self.hy, self.ax, self.ey, self.bx, self.ey, self.bx, self.fy, self.dx, self.gy, self.fx, self.gy, self.hx, self.hy]) elif re.match("[MOD]{3}", surfaces): shape = QtGui.QPolygon([self.ax, self.cy, self.cx, self.dy, self.innerRect.topLeft().x(),self.innerRect.topLeft().y(), self.innerRect.topRight().x(),self.innerRect.topRight().y(), self.gx, self.dy, self.ix, self.cy, self.ix, self.gy, self.gx, self.fy, self.innerRect.bottomRight().x(), self.innerRect.bottomRight().y(), self.innerRect.bottomLeft().x(), self.innerRect.bottomLeft().y(),self.cx, self.fy, self.ax, self.gy]) elif re.match("[MO]{2}", surfaces): shape = QtGui.QPolygon([self.innerRect.topLeft().x(), self.innerRect.topLeft().y(), self.innerRect.topRight().x(),self.innerRect.topRight().y(), self.gx, self.dy, self.ix, self.cy, self.ix, self.gy, self.gx, self.fy, self.innerRect.bottomRight().x(), self.innerRect.bottomRight().y(), self.innerRect.bottomLeft().x(), self.innerRect.bottomLeft().y(), ]) elif re.match("[DO]{2}", surfaces): shape = QtGui.QPolygon([self.ax, self.cy, self.cx, self.dy, self.innerRect.topLeft().x(),self.innerRect.topLeft().y(), self.innerRect.topRight().x(),self.innerRect.topRight().y(), self.innerRect.bottomRight().x(), self.innerRect.bottomRight().y(), self.innerRect.bottomLeft().x(), self.innerRect.bottomLeft().y(), self.cx, self.fy, self.ax, self.gy]) elif "O" in surfaces: shape = QtGui.QPolygon([self.innerRect.topLeft().x(), self.innerRect.topLeft().y(), self.innerRect.topRight().x(), self.innerRect.topRight().y(), self.innerRect.bottomRight().x(), self.innerRect.bottomRight().y(), self.innerRect.bottomLeft().x(), self.innerRect.bottomLeft().y()]) elif "M" in surfaces: shape = QtGui.QPolygon( [self.hx, self.dy, self.ix, self.dy, self.ix, self.fy, self.hx, self.fy, self.gx, self.ey]) elif "D" in surfaces: shape = QtGui.QPolygon( [self.ax, self.dy, self.bx, self.dy, self.cx, self.ey, self.bx, self.fy, self.ax, self.fy]) elif "L" in surfaces: shape = QtGui.QPolygon([self.cx, self.hy, self.cx, self.gy, self.ex, self.fy, self.gx, self.gy, self.gx, self.hy, self.fx, self.iy, self.dx, self.iy]) elif "B" in surfaces: shape = QtGui.QPolygon([self.cx, self.cy, self.cx, self.ay, self.ex, self.ay, self.gx, self.ay, self.gx, self.cy, self.fx, self.dy, self.dx, self.dy]) if shape: self.fill_shapes.append((shape, brush)) else: LOGGER.debug ("shape error! '%s'"% surfaces) self.fill_shapes_current = True
[docs] def draw_structure(self, painter): if self.is_present: self.init_restoration_shapes() xpad, ypad = 6, 6 painter.save() painter.setBrush(colours.IVORY) painter.drawRoundedRect(self.rect, xpad, ypad) painter.drawRect(self.innerRect) outer_rect = self.rect.adjusted(xpad/3,ypad/3, -xpad/3, -ypad/3) painter.drawLine(outer_rect.topLeft(), self.innerRect.topLeft()) painter.drawLine(outer_rect.topRight(), self.innerRect.topRight()) painter.drawLine(outer_rect.bottomLeft(), self.innerRect.bottomLeft()) painter.drawLine(outer_rect.bottomRight(), self.innerRect.bottomRight()) painter.restore() else: xpad, ypad = 6, 6 painter.save() painter.setOpacity(0.6) painter.drawRoundedRect(self.rect, xpad, ypad) painter.restore()
[docs] def draw_restorations(self, painter): if self.is_present: self.init_restoration_shapes() for crown in self.crowns: painter.save() option = QtGui.QTextOption(QtCore.Qt.AlignCenter) painter.setPen(QtGui.QPen(QtCore.Qt.black)) painter.drawEllipse(self.rect) painter.drawText(self.rect, crown.crown_type, option) painter.restore() for shape, brush in self.fill_shapes: painter.save() #--put an outline around the filling painter.setPen(QtGui.QPen(colours.FILL_OUTLINE, 1)) if brush: painter.setBrush(brush) painter.drawPolygon(shape) painter.restore()
def _test_draw_graphics_points(self, painter): ''' test code ''' painter.save() painter.setPen(QtGui.QPen(QtGui.QColor('red'))) for xpoint in ( self.ax, self.bx, self.cx, self.dx, self.ex, self.fx, self.gx, self.hx, self.ix): for ypoint in ( self.ay, self.by, self.cy, self.dy, self.ey, self.fy, self.gy, self.hy, self.iy): painter.drawPoint(QtCore.QPointF(xpoint, ypoint)) painter.restore()
if __name__ == "__main__": logging.basicConfig(level = logging.DEBUG) from lib_openmolar.client.qt4.widgets import chart_widgets def paintEvent(event): painter = QtGui.QPainter(widg) painter.setRenderHint(QtGui.QPainter.Antialiasing, True) for tooth in teeth: tooth.draw_structure(painter) tooth.draw_restorations(painter) tooth._test_draw_graphics_points(painter) app = QtGui.QApplication([]) widg = QtGui.QWidget() widg.setFixedSize(600,600) teeth = [] model = chart_widgets.ChartDataModel() rects = [ widg.rect().adjusted(0,0,-widg.width()/2, -widg.height()/2), widg.rect().adjusted(widg.width()/2, 0, 0, -widg.height()/2), widg.rect().adjusted(0,widg.height()/2, -widg.width()/2, 0), widg.rect().adjusted(widg.width()/2, widg.height()/2, 0, 0) ] ids = [1,16,17,32] for rect in rects: tooth = ChartTooth(ids[rects.index(rect)], model) tooth.set_rect(rect) tooth.set_graphics_points() teeth.append(tooth) for fill in ("O,CO", "FS,AM"): #("ODB,CO", "B,GL", "DL,AM", "OL,CO", "FS"): for tooth_id in ids: prop = chart_widgets.ToothData(tooth_id) prop.from_fill_string(fill) model.add_property(prop) #model.endResetModel() widg.paintEvent = paintEvent widg.show() app.exec_()