# Code to complete the various primitive types
# Users should never import this module directly. All of the public types and
# functions will be explicitly imported by __init__.py
from __future__ import division
import cvisual
from time import sleep
from cvisual import vector
import crayola
color = crayola
from math import pi
from numpy import *
NCHORDS = 20.0 # number of chords in one coil of a helix
# Scenegraph management:
# Renderable objects which become visible need to be added
# to the scene graph using EITHER
# - display.add_renderable() if not in a frame, OR
# - frame.add_renderable()
# Renderable objects which become invisible need to be removed using
# the corresponding remove_renderable function.
# If the display or frame of a visible object changes, it needs to
# be removed and added.
# The py_renderable class encapsulates this logic, as well as
# a fair amount of construction and attribute access common to
# all renderables.
class py_renderable(object):
def __init__(self, **keywords):
_other = keywords.get("_other")
if _other:
del keywords["_other"]
super(py_renderable,self).__init__(_other)
self.__dict__ = dict(_other.__dict__)
self.__display = _other.display
self.__frame = _other.frame
self.__visible = _other.visible
else:
super(py_renderable,self).__init__()
self.__display = cvisual.display.get_selected()
self.__frame = None
self.__visible = True
if keywords.has_key('display'):
self.__display = keywords['display']
del keywords['display']
if keywords.has_key('visible'):
self.__visible = keywords['visible']
del keywords['visible']
if keywords.has_key('frame'):
self.__frame = keywords['frame']
del keywords['frame']
if not _other: self.init_defaults(keywords)
self.process_init_args_from_keyword_dictionary( keywords )
if self.__frame:
if self.__frame.display != self.__display:
raise ValueError, "Cannot initialize an object with a frame on a different display."
self.check_init_invariants()
if self.__visible:
if self.__frame:
self.__frame.add_renderable(self)
elif self.__display:
self.__display.add_renderable(self)
def __copy__( self, **keywords):
return self.__class__(_other=self, **keywords)
def check_init_invariants(self):
pass
def set_display(self, display):
"For internal use only. The setter for the display property."
if display != self.__display:
# Check that we aren't screwing up a frame.
if self.__frame:
raise ValueError, """Cannot change displays when within a
frame. Make frame None, first."""
if self.__display:
self.__display.remove_renderable(self)
self.__display = display
self.__display.add_renderable(self)
def get_display(self):
"For internal use only. The getter for the display property."
return self.__display
display = property( get_display, set_display)
def get_frame(self):
"For internal use only. The getter for the frame property."
return self.__frame
# Overridden by the frame class below to add extra checks.
def set_frame(self, frame):
"For internal use only. The setter for the frame property."
if frame != self.__frame:
if frame.display != self.__display:
raise ValueError, "Cannot set to a frame on a different display."
if frame and self.__frame:
# Simply moving from one frame to another.
self.__frame.remove_renderable(self)
frame.add_renderable(self)
elif frame and not self.__frame:
# Moving into a reference frame when otherwise not in one.
if self.__display:
self.__display.remove_renderable(self)
frame.add_renderable(self)
elif not frame and self.__frame:
# Removing from a reference frame.
self.__frame.remove_renderable(self)
if self.__display:
self.__display.add_renderable(self)
self.__frame = frame
frame = property( get_frame, set_frame)
def get_visible(self):
"For internal use only. The getter for the visible property."
return self.__visible
def set_visible(self, visible):
"For internal use only. The setter for the visible property."
if visible and not self.__visible:
if self.__frame:
self.__frame.add_renderable(self)
elif self.__display:
self.__display.add_renderable(self)
if not visible and self.__visible:
if self.__frame:
self.__frame.remove_renderable(self)
elif self.__display:
self.__display.remove_renderable(self)
self.__visible = visible
visible = property( get_visible, set_visible)
def init_defaults(self, keywords):
self.color = self.display.foreground
if isinstance(self, cvisual.light):
self.color = (1,1,1)
elif 'material' not in keywords:
self.material = self.display.material
def process_init_args_from_keyword_dictionary( self, keywords ):
if 'axis' in keywords: #< Should be set before 'length'
self.axis = keywords['axis']
del keywords['axis']
# Assign all other properties
for key, value in keywords.iteritems():
setattr(self, key, value)
class py_renderable_uniform (py_renderable):
def check_init_invariants(self):
if not self.display.uniform:
raise RuntimeError, "Do not create " + self.__class__.__name__ + " with nonuniform axes."
class py_renderable_arrayobject (py_renderable):
# Array objects are likely to need special handling in various places
def get_red( self):
return self.color[:,0]
def get_green(self):
return self.color[:,1]
def get_blue(self):
return self.color[:,2]
def get_x(self):
return self.pos[:,0]
def get_y(self):
return self.pos[:,1]
def get_z(self):
return self.pos[:,2]
# Also none of them support opacity yet.
def set_opacity(self, opacity):
raise RuntimeError, "Cannot yet specify opacity for curve, faces, convex, or points."
opacity = property( None, set_opacity, None)
################################################################################
# Complete each type.
class distant_light (py_renderable, cvisual.distant_light):
def set_pos(self, _): raise AttributeError("Attempt to set pos of a distant_light object.")
pos = property(None,set_pos)
class local_light (py_renderable, cvisual.local_light):
def set_direction(self, _): raise AttributeError("Attempt to set direction of a local_light object.")
direction = property(None,set_direction)
class arrow (py_renderable_uniform, cvisual.arrow):
pass
class cone (py_renderable_uniform, cvisual.cone):
pass
class cylinder (py_renderable_uniform, cvisual.cylinder):
pass
class sphere (py_renderable_uniform, cvisual.sphere):
pass
class ring (py_renderable_uniform, cvisual.ring):
pass
class box (py_renderable_uniform, cvisual.box):
pass
class ellipsoid (py_renderable_uniform, cvisual.ellipsoid):
pass
class pyramid (py_renderable_uniform, cvisual.pyramid ):
pass
class label (py_renderable, cvisual.label):
def init_defaults( self, keywords ):
if not keywords.has_key('linecolor'):
self.linecolor = self.display.foreground
super(label, self).init_defaults( keywords )
class frame (py_renderable_uniform, cvisual.frame):
def set_frame(self, frame):
#Check to ensure that we are not establishing a cycle of reference frames.
frame_iterator = frame
while frame_iterator:
if frame_iterator.frame is self:
raise ValueError, "Attempted to create a cycle of reference frames."
frame_iterator = frame_iterator.frame
py_renderable_uniform.set_frame( self, frame)
class curve ( py_renderable_arrayobject, cvisual.curve ):
pos = property( cvisual.curve.get_pos, cvisual.curve.set_pos, None)
color = property( cvisual.curve.get_color, cvisual.curve.set_color, None)
x = property( py_renderable_arrayobject.get_x, cvisual.curve.set_x, None)
y = property( py_renderable_arrayobject.get_y, cvisual.curve.set_y, None)
z = property( py_renderable_arrayobject.get_z, cvisual.curve.set_z, None)
red = property( py_renderable_arrayobject.get_red, cvisual.curve.set_red, None)
green = property( py_renderable_arrayobject.get_green, cvisual.curve.set_green, None)
blue = property( py_renderable_arrayobject.get_blue, cvisual.curve.set_blue, None)
class points ( py_renderable_arrayobject, cvisual.points ):
pos = property( cvisual.points.get_pos, cvisual.points.set_pos, None)
color = property( cvisual.points.get_color, cvisual.points.set_color, None)
x = property( py_renderable_arrayobject.get_x, cvisual.points.set_x, None)
y = property( py_renderable_arrayobject.get_y, cvisual.points.set_y, None)
z = property( py_renderable_arrayobject.get_z, cvisual.points.set_z, None)
red = property( py_renderable_arrayobject.get_red, cvisual.points.set_red, None)
green = property( py_renderable_arrayobject.get_green, cvisual.points.set_green, None)
blue = property( py_renderable_arrayobject.get_blue, cvisual.points.set_blue, None)
class convex( py_renderable_arrayobject, py_renderable_uniform, cvisual.convex ):
pos = property( cvisual.convex.get_pos, cvisual.convex.set_pos, None)
class faces( py_renderable_arrayobject, cvisual.faces ):
pos = property( cvisual.faces.get_pos, cvisual.faces.set_pos, None)
normal = property( cvisual.faces.get_normal, cvisual.faces.set_normal, None)
color = property( cvisual.faces.get_color, cvisual.faces.set_color, None)
red = property( py_renderable_arrayobject.get_red, cvisual.faces.set_red, None)
green = property( py_renderable_arrayobject.get_green, cvisual.faces.set_green, None)
blue = property( py_renderable_arrayobject.get_blue, cvisual.faces.set_blue, None)
class helix(py_renderable):
def __init__( self, _other=None, pos=vector(),
x=None, y=None, z=None, red=None, green=None, blue=None,
axis=vector(1,0,0), radius=1.0, length=None, up=vector(0,1,0),
coils=5, thickness=None, color=color.white, visible=True, **keywords):
if keywords.has_key('display'):
disp = keywords['display']
del keywords['display']
else:
disp = cvisual.display.get_selected()
if (not disp.uniform):
raise RuntimeError, "Do not create helix with nonuniform axes."
if keywords.has_key('frame'):
fr = keywords['frame']
del keywords['frame']
else:
fr = None
self.process_init_args_from_keyword_dictionary( keywords )
if x is not None:
pos[0] = x
if y is not None:
pos[1] = y
if z is not None:
pos[2] = z
if red is not None:
color[0] = red
if green is not None:
color[1] = green
if blue is not None:
color[2] = blue
self.__color = color
axis = vector(axis)
if length is None:
length = axis.mag
self.__length = length
self.__axis = axis
self.__radius = radius
self.__up = up
self.__coils = coils
self.__thickness = radius/20.
if thickness:
self.__thickness = thickness
self.__frame = frame(display=disp, frame=fr, pos=pos, axis=axis.norm(), up=up)
self.helix = curve( frame = self.__frame, radius = self.__thickness/2.,
color = color)
self.create_pos()
def create_pos(self):
k = self.coils*(2*pi/self.__length)
dx = (self.length/self.coils)/NCHORDS
x_col = arange(0, self.__length+dx, dx)
pos_data = zeros((len(x_col),3), float64)
pos_data[:,0] = arange(0, self.__length+dx, dx)
pos_data[:,1] = (self.radius) * sin(k*pos_data[:,0])
pos_data[:,2] = (self.radius) * cos(k*pos_data[:,0])
self.helix.pos = pos_data
def set_pos(self, pos):
self.__frame.pos = vector(pos)
def get_pos(self):
return self.__frame.pos
def set_x(self, x):
self.__frame.pos.x = x
def get_x(self):
return self.__frame.pos.x
def set_y(self, y):
self.__frame.pos.y = y
def get_y(self):
return self.__frame.pos.y
def set_z(self, z):
self.__frame.pos.z = z
def get_z(self):
return self.__frame.pos.z
def set_color(self, color):
self.__color = self.helix.color = color
def get_color(self):
return self.__color
def set_red(self, red):
self.helix.red = red
def get_red(self):
return self.helix.red
def set_green(self, green):
self.helix.green = green
def get_green(self):
return self.helix.green
def set_blue(self, blue):
self.helix.blue = blue
def get_blue(self):
return self.helix.blue
def set_radius(self, radius):
scale = radius/self.__radius
self.__radius = radius
self.helix.y *= scale
self.helix.z *= scale
def get_radius(self):
return self.__radius
def set_axis(self, axis):
axis = vector(axis)
self.__axis = axis
self.__frame.axis = axis.norm()
self.set_length(axis.mag)
def get_axis(self):
return self.__axis
def set_length(self, length):
self.helix.x *= (length/self.__length)
self.__length = length
self.__frame.axis = self.__axis.norm()
self.__axis = length*self.__frame.axis
def get_length(self):
return self.__length
def set_coils(self, coils):
if self.__coils == coils: return
self.__coils = coils
self.create_pos()
def get_coils(self):
return self.__coils
def set_thickness(self, thickness):
if self.__thickness == thickness: return
self.__thickness = thickness
self.helix.radius = thickness/2.
def get_thickness(self):
return self.__thickness
def set_display(self, disp):
self.helix.display = self.frame.display = disp
def get_display(self):
return self.helix.display
def set_frame(self, fr):
self.__frame.frame = fr
def get_frame(self):
return self.__frame.frame
def set_up(self, up):
self.__frame.up = up
def get_up(self):
return self.__frame.up
def set_visible(self, visible):
self.helix.visible = visible
def get_visible(self):
return self.helix.visible
pos = property( get_pos, set_pos, None)
x = property( get_x, set_x, None)
y = property( get_y, set_y, None)
z = property( get_z, set_z, None)
color = property( get_color, set_color, None)
red = property( get_red, set_red, None)
green = property( get_green, set_green, None)
blue = property( get_blue, set_blue, None)
axis = property( get_axis, set_axis, None)
radius = property( get_radius, set_radius, None)
coils = property( get_coils, set_coils, None)
thickness = property( get_thickness, set_thickness, None)
length = property( get_length, set_length, None)
display = property( get_display, set_display, None)
frame = property( get_frame, set_frame, None)
up = property( get_up, set_up, None)
visible = property( get_visible, set_visible, None)
import os, sys, glob
## The following code, from here to the end of findSystemFonts, is from
## https://mail.enthought.com/pipermail/enthought-svn/2005-September/000724.html
## Authors: John Hunter <jdhunter at ace.bsd.uchicago.edu>
## Paul Barrett <Barrett at STScI.Edu>
# OS Font paths
MSFolders = \
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
MSFontDirectories = [
r'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts',
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Fonts']
X11FontDirectories = [
# what seems to be the standard installation point
"/usr/X11R6/lib/X11/fonts/TTF/",
# documented as a good place to install new fonts...
"/usr/share/fonts/",
# common application, not really useful
"/usr/lib/openoffice/share/fonts/truetype/",
]
OSXFontDirectories = [
"/Library/Fonts/",
"/Network/Library/Fonts/",
"/System/Library/Fonts/"
]
home = os.environ.get('HOME')
if home is not None:
# user fonts on OSX
path = os.path.join(home, 'Library', 'Fonts')
OSXFontDirectories.append(path)
def win32FontDirectory():
"""Return the user-specified font directory for Win32."""
try:
import _winreg
except ImportError:
return os.path.join(os.environ['WINDIR'], 'Fonts')
else:
user = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, MSFolders)
try:
return _winreg.QueryValueEx(user, 'Fonts')[0]
finally:
_winreg.CloseKey(user)
return None
def win32InstalledFonts(directory=None, fontext='ttf'):
"""Search for fonts in the specified font directory, or use the
system directories if none given. A list of TrueType fonts are
returned by default with AFM fonts as an option.
"""
import _winreg
if directory is None:
directory = win32FontDirectory()
key, items = None, {}
for fontdir in MSFontDirectories:
try:
local = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, fontdir)
except OSError:
continue
if not local:
return glob.glob(os.path.join(directory, '*.'+fontext))
try:
for j in range(_winreg.QueryInfoKey(local)[1]):
try:
key, direc, any = _winreg.EnumValue( local, j)
if not os.path.dirname(direc):
direc = os.path.join(directory, direc)
direc = os.path.abspath(direc).lower()
if direc[-4:] == '.'+fontext:
items[direc] = 1
except EnvironmentError:
continue
except WindowsError:
continue
return items.keys()
finally:
_winreg.CloseKey(local)
return None
def OSXFontDirectory():
"""Return the system font directories for OS X."""
fontpaths = []
def add(arg,directory,files):
fontpaths.append(directory)
for fontdir in OSXFontDirectories:
try:
if os.path.isdir(fontdir):
os.path.walk(fontdir, add, None)
except (IOError, OSError, TypeError, ValueError):
pass
return fontpaths
def OSXInstalledFonts(directory=None, fontext=None):
"""Get list of font files on OS X - ignores font suffix by default"""
if directory is None:
directory = OSXFontDirectory()
files = []
for path in directory:
if fontext is None:
files.extend(glob.glob(os.path.join(path,'*')))
else:
files.extend(glob.glob(os.path.join(path, '*.'+fontext)))
files.extend(glob.glob(os.path.join(path, '*.'+fontext.upper())))
return files
def x11FontDirectory():
"""Return the system font directories for X11."""
fontpaths = []
def add(arg,directory,files):
fontpaths.append(directory)
for fontdir in X11FontDirectories:
try:
if os.path.isdir(fontdir):
os.path.walk(fontdir, add, None)
except (IOError, OSError, TypeError, ValueError):
pass
return fontpaths
def findSystemFonts(fontpaths=None, fontext='ttf'):
"""Search for fonts in the specified font paths, or use the system
paths if none given. A list of TrueType fonts are returned by default
with AFM fonts as an option.
"""
fontfiles = {}
if fontpaths is None:
if sys.platform == 'win32':
fontdir = win32FontDirectory()
fontpaths = [fontdir]
# now get all installed fonts directly...
for f in win32InstalledFonts(fontdir):
base, ext = os.path.splitext(f)
if len(ext)>1 and ext[1:].lower()==fontext:
fontfiles[f] = 1
else:
fontpaths = x11FontDirectory()
# check for OS X & load its fonts if present
if sys.platform == 'darwin':
for f in OSXInstalledFonts(fontext=fontext):
fontfiles[f] = 1
elif isinstance(fontpaths, (str, unicode)):
fontpaths = [fontpaths]
for path in fontpaths:
files = glob.glob(os.path.join(path, '*.'+fontext))
files.extend(glob.glob(os.path.join(path, '*.'+fontext.upper())))
for fname in files:
fontfiles[os.path.abspath(fname)] = 1
return [fname for fname in fontfiles.keys() if os.path.exists(fname)]
def findFont(font):
flist = findSystemFonts()
if font == "serif":
font = "Times New Roman"
elif font == "sans-serif" or font == "sans":
font = "Verdana"
elif font == "monospace":
font = "Courier New"
f = font.split('.')
if f[-1] == 'ttf':
font = font[:-4]
font = font.split('/')[-1]
font = font.split('\\')[-1]
font = font.lower()+".ttf"
match = None
sans = None
freesans = freeserif = freemono = None
for f in flist:
if f[-4:] != ".ttf": continue
if sys.platform == 'win32':
name = f.split('\\')[-1].lower()
else:
name = f.split('/')[-1].lower()
if name == font:
match = f
break
elif name == "verdana.ttf":
sans = f
elif name == "freesans.ttf": # Ubuntu Linux
freesans = f
elif name == "freeserif.ttf": # Ubuntu Linux
freeserif = f
elif name == "freemono.ttf": # Ubuntu Linux
freemono = f
elif len(font) > len(name):
if name[:-4] == font[:len(name)-4]:
if match is None:
match = f
elif len(match) < len(name):
match = f
elif len(name) > len(font):
if font[:-4] == name[:len(font)-4]:
if match is None:
match = f
elif len(match) < len(font):
match = f
if freesans and not sans:
sans = freesans
if freesans and not match and font.lower()[:7] == "verdana":
match = freesans
elif freeserif and not match and font.lower()[:5] == "times":
match = freeserif
elif freemono and not match and font.lower()[:7] == "courier":
match = freemono
if match is not None:
return match
elif sans:
return sans
else:
raise ValueError, 'Cannot find font "'+font+'"'
class text(object):
def __init__(self, pos=(0,0,0), axis=(1,0,0), up=(0,1,0), height=1, color=color.white,
x=None, y=None, z=None, red=None, green=None, blue=None,
text="", font="TIMES", align='left', visible=True,
depth=0.2, material=None, spacing=0.03, **keywords):
self.initial = True
self.__text = text
if keywords.has_key('display'):
self.__display = keywords['display']
del keywords['display']
else:
self.__display = cvisual.display.get_selected()
if (not self.display.uniform):
raise RuntimeError, "Do not create 3D text with nonuniform axes."
if keywords.has_key('frame'):
fr = keywords['frame']
del keywords['frame']
else:
fr = None
if x is not None:
pos[0] = x
if y is not None:
pos[1] = y
if z is not None:
pos[2] = z
if red is not None:
color[0] = red
if green is not None:
color[1] = green
if blue is not None:
color[2] = blue
self.__color = color
self.__material = material
self.__visible = visible
self.__up = up
self.__font = font
self.__height = float(height)
self.__spacing = spacing
try:
if len(depth) == 3:
self.__depth = vector(depth)
except:
self.__depth = vector(0,0,depth)
self.__frame = frame(display=self.__display, frame=fr, pos=pos, axis=vector(axis).norm(), up=up)
self.backframe = []
self.frontframe = []
self.check_align(align)
self.createText()
self.initial = False
def fontlist(self):
return findSystemFonts()
def createText(self):
self.lines = self.__text.split('\n')
while self.lines[-1] == '\n': # strip off trailing newlines
self.lines = self.lines[:-1]
try:
self.getGlyphContour()
except:
raise ValueError, 'Something is wrong with font '+self.__font
self.paintText()
def destroyText(self):
# A contour failure could have left front/back/sides uncreated
for line in range(len(self.lines)):
try:
self.back[line].visible = False
except:
pass
try:
self.front[line].visible = False
except:
pass
try:
self.sides[line].visible = False
except:
pass
def getGlyphContour(self):
import Polygon
from ttfquery import describe,glyphquery,glyph
font = describe.openFont(findFont(self.__font))
try:
fonth = glyphquery.charHeight(font)
except:
fonth = 1000
if fonth == 0: fonth = 1000
try:
desc = glyphquery.charDescent(font) # charDescent may not be present
fontheight = fonth+desc
self.fontscale = 1./fontheight
self.__descent = self.fontscale*desc
except:
self.__descent = -0.3*self.__height # approximate value
fontheight = 0.7*fonth
self.fontscale = 1.3/fontheight
self.__vertical_spacing = self.fontscale*self.__height*glyphquery.lineHeight(font)
excludef_list = [("ITCEdscr")]
excludec_list = [("FRSCRIPT", "A"), ("jokerman", "O"),
("vivaldii", "O"), ("vivaldii", "Q"),
("vivaldii", "R")]
self.ptext = []
self.polies = []
self.__upper_left = []
self.__upper_right = []
self.__lower_left = []
self.__lower_right = []
self.__widths = []
for line in range(len(self.lines)):
self.ptext.append(Polygon.Polygon())
self.polies.append([])
self.backframe.append(frame(frame=self.__frame))
self.frontframe.append(frame(frame=self.__frame))
bb = 0
for newchar in self.lines[line]:
if newchar == " ":
try:
if a:
bx = a.boundingBox()
bba = bx[1]-bx[0]
bba = min(bba, 700)
bb += bba
except:
pass
continue
n = glyphquery.glyphName(font, newchar)
if n == ".notdef":
print "The character '"+newchar+"' is not supported in the font "+self.__font
continue
g = glyph.Glyph(n)
c = g.calculateContours(font)
contours = []
for contour in c:
contours.append(glyph.decomposeOutline(contour))
if len(contours[-1]) == 0: contours.pop()
for contour in contours:
pp = 0
for i in range(len(contour)-1):
if (contour[i][0] == contour[i+1][0]) and (contour[i][1] == contour[i+1][1]):
contour.pop(i)
pp += 1
if i+pp >= len(contour)-1: break
def lenctr(contour):
totlen = 0
for j in range(len(contour)-1):
totlen += (vector(contour[j])-vector(contour[j+1])).mag
return totlen
lc = len(contours)
if lc >= 4:
mli = 0
maxl = 0
lengths = []
for i in range(len(contours)):
totlen = lenctr(contours[i])
lengths.append((totlen,i))
if totlen > maxl:
mli = i
maxl = totlen
lengths.sort()
lengths.reverse()
ocontours = []
for ll in lengths:
ocontours.append(contours[ll[1]])
contours = ocontours
indxf = -1
indxc = -1
if (mli > 0 and newchar != "%"):
try: indxf = excludef_list.index(self.__font)
except: pass
if indxf == -1:
try: indxc = excludec_list.index((self.__font, newchar))
except: pass
if (indxf == -1 and indxc == -1):
maxc = contours.pop(mli)
contours.insert(0, maxc)
a = Polygon.Polygon(contours[0])
for i in range(1,len(contours)):
b = Polygon.Polygon(contours[i])
if a.covers(b): a = a - b
elif b.covers(a): a = b - a
else: a = a + b
for contour in contours:
self.polies[line].append(array(contour)++array([bb - a.boundingBox()[0],0]))
a.shift(bb - a.boundingBox()[0] ,0)
self.ptext[line] += a
bx = self.ptext[line].boundingBox()
bb = bx[1] - bx[0] + self.__spacing*fontheight
offset = vector(0,-line*self.__vertical_spacing,0)
self.__upper_left.append(self.fontscale*self.__height*vector(self.ptext[line].boundingBox()[0],self.ptext[line].boundingBox()[3],0)+offset)
self.__upper_right.append(self.fontscale*self.__height*vector(self.ptext[line].boundingBox()[1],self.ptext[line].boundingBox()[3],0)+offset)
self.__lower_left.append(self.fontscale*self.__height*vector(self.ptext[line].boundingBox()[0],self.ptext[line].boundingBox()[2],0)+offset)
self.__lower_right.append(self.fontscale*self.__height*vector(self.ptext[line].boundingBox()[1],self.ptext[line].boundingBox()[2],0)+offset)
self.__widths.append((self.__lower_right[-1]-self.__upper_left[-1]).dot(vector(1,0,0)))
def alignText(self):
for line in range(len(self.lines)):
v = vector(0,-line*self.__vertical_spacing,0)
if self.__align == 'left':
self.backframe[line].pos = v
self.frontframe[line].pos = self.__depth+v
elif self.__align == 'right':
self.backframe[line].pos = -vector(self.__widths[line],0,0)+v
self.frontframe[line].pos = self.__depth - vector(self.__widths[line],0,0)+v
elif self.__align == 'center':
self.backframe[line].pos = -0.5*vector(self.__widths[line],0,0)+v
self.frontframe[line].pos = self.__depth - 0.5*vector(self.__widths[line],0,0)+v
# If extruding backward, move both back and front faces back, so front face is at specified pos
if self.__depth.z < 0:
self.backframe[line].pos += self.__depth
self.frontframe[line].pos -= self.__depth
def paintText(self):
if not self.initial:
self.destroyText()
self.back = []
self.front = []
self.sides = []
for line in range(len(self.lines)):
atri= self.ptext[line].triStrip()
nvertices = 0
for i in range(len(atri)):
nvertices += 3*(len(atri[i])-2)
vfront = zeros((nvertices, 2), float)
vback = zeros((nvertices, 2), float)
n = 0
for i in range(len(atri)):
for j in range(len(atri[i])-2):
if j%2:
vfront[n:n+3] = (atri[i][j], atri[i][j+2], atri[i][j+1])
vback[n:n+3] = atri[i][j:j+3]
else:
vback[n:n+3] = (atri[i][j], atri[i][j+2], atri[i][j+1])
vfront[n:n+3] = atri[i][j:j+3]
n += 3
self.alignText()
scale = self.fontscale*self.__height
vnormals = zeros((nvertices, 3), float)
vnormals[...,2] = 1.0
self.back.append(faces(frame=self.backframe[line], pos=scale*vback, normal=-vnormals,
color=self.__color, material=self.__material, visible=self.__visible))
self.front.append(faces(frame=self.frontframe[line], pos=scale*vfront, normal=vnormals,
color=self.__color, material=self.__material, visible=self.__visible))
if self.__depth.mag != 0:
nvertices = 0
for i in range(len(self.polies[line])):
nvertices += 6*len(self.polies[line][i])
vsides = zeros((nvertices, 3), float)
vnormals = zeros((nvertices, 3), float)
n = 0
if self.__depth.z < 0: # adjust for case of backward extrusion
shift = -self.__depth
else:
shift = self.__depth
for i in range(len(self.polies[line])):
polyi = self.polies[line][i]
nstart = n
for j in range(len(polyi)-1):
v1 = scale*vector(polyi[j])
v2 = scale*vector(polyi[j])+shift
v3 = scale*vector(polyi[j+1])
v4 = scale*vector(polyi[j+1])+shift
if (v3-v1).mag == 0.: continue
vsides[n:n+6] = (v1, v2, v3, v3, v2, v4)
## Create smooth normals on the sides
newnormal = (vector(polyi[j])-vector(polyi[j+1])).cross(self.__depth).norm()
if j > 0 and newnormal.dot(oldnormal) > 0.8:
vnormals[n-4] = vnormals[n-3] = vnormals[n-1] = vnormals[n:n+6] = (0.5*(oldnormal+newnormal)).norm()
else:
vnormals[n:n+6] = newnormal
oldnormal = newnormal
n += 6
# Check to see whether we came around to the original location
if (vector(polyi[-1])-vector(polyi[0])).mag == 0. and newnormal.dot(vnormals[nstart]) > 0.8:
vnormals[n-4] = vnormals[n-3] = vnormals[n-1] = vnormals[nstart:nstart+2] = \
vnormals[nstart+4] = vector(0.5*(vnormals[nstart]+newnormal)).norm()
self.sides.append(faces(frame=self.backframe[line], pos=vsides, normal=vnormals,
color=self.__color, material=self.__material, visible=self.__visible))
def rotate(self, angle=0, axis=None, origin=None):
if axis is None:
axis = self.__display.up
if origin is None:
origin = self.__frame.pos
self.__frame.rotate(origin=vector(origin), angle=angle, axis=vector(axis))
def set_text(self, text):
if self.__text == text: return
self.destroyText()
self.__text = text
self.createText()
def get_text(self):
return self.__text
def check_align(self, align):
if align == 'left' or align == 'right' or align == 'center':
self.__align = align
else:
raise ValueError, "align must be 'left', 'right', or 'center'"
def set_align(self, align):
self.check_align(align)
self.alignText()
def get_align(self):
return self.__align
def set_font(self, font):
if self.__font == font: return
self.__font = font
self.createText()
def get_font(self):
return self.__font
def set_height(self, height):
height = float(height)
if height == self.height: return
oldheight = self.__height
self.__height = height
factor = height/oldheight
self.__descent *= factor
for line in range(len(self.lines)):
self.back[line].pos *= factor
self.front[line].pos *= factor
if self.__depth.mag > 0: # maybe we didn't create any sides (0 depth)
self.sides[line].pos[...,0] *= factor
self.sides[line].pos[...,1] *= factor
self.__widths[line] *= factor
self.__upper_left[line] *= factor
self.__upper_right[line] *= factor
self.__lower_right[line] *= factor
self.__lower_left[line] *= factor
self.set_vertical_spacing(factor*self.__vertical_spacing)
self.alignText()
def get_height(self):
return self.__height
def set_width(self, temp):
raise AttributeError, "Cannot set the width"
def get_width(self):
return max(self.__widths)
def set_widths(self, temp):
raise AttributeError, "Cannot set the widths"
def get_widths(self):
return self.__widths
def set_descent(self, temp):
raise AttributeError, "Cannot set the descent"
def get_descent(self):
return abs(self.__descent)
def set_spacing(self, spacing):
if self.__spacing == spacing: return
self.__spacing = spacing
self.createText()
def get_spacing(self):
return self.__spacing
def set_vertical_spacing(self, vspace):
old = self.__vertical_spacing
if vspace == old: return
for line in range(1,len(self.lines)):
self.backframe[line].y -= line*(vspace-old)
self.frontframe[line].y -= line*(vspace-old)
self.__vertical_spacing = vspace
def get_vertical_spacing(self):
return self.__vertical_spacing
def set_upper_left(self, temp):
raise AttributeError, "Cannot set upper_left"
def get_upper_left(self):
y = self.__upper_left[0].y
if self.__align == 'left':
x = 0
elif self.__align == 'right':
x = -self.get_width()
elif self.__align == 'center':
x = - 0.5*self.get_width()
return self.__frame.frame_to_world(vector(x,y,0))
def set_upper_right(self, temp):
raise AttributeError, "Cannot set upper_right"
def get_upper_right(self):
y = self.__upper_left[0].y
if self.__align == 'left':
x = self.get_width()
elif self.__align == 'right':
x = 0
elif self.__align == 'center':
x = 0.5*self.get_width()
return self.__frame.frame_to_world(vector(x,y,0))
def set_lower_left(self, temp):
raise AttributeError, "Cannot set lower_left"
def get_lower_left(self):
y = self.__lower_left[-1].y
if self.__align == 'left':
x = 0
elif self.__align == 'right':
x = -self.get_width()
elif self.__align == 'center':
x = - 0.5*self.get_width()
return self.__frame.frame_to_world(vector(x,y,0))
def set_lower_right(self, temp):
raise AttributeError, "Cannot set lower_right"
def get_lower_right(self):
y = self.__lower_left[-1].y
if self.__align == 'left':
x = self.get_width()
elif self.__align == 'right':
x = 0
elif self.__align == 'center':
x = 0.5*self.get_width()
return self.__frame.frame_to_world(vector(x,y,0))
def set_start(self, temp):
raise AttributeError, "Cannot set start"
def get_start(self):
if self.__align == 'left':
x = 0
elif self.__align == 'right':
x = -self.get_width()
elif self.__align == 'center':
x = -0.5*self.get_width()
return self.__frame.frame_to_world(vector(x,0,0))
def set_starts(self, temp):
raise AttributeError, "Cannot set starts"
def get_starts(self):
starts = []
for line in range(len(self.lines)):
if self.__align == 'left':
x = 0
elif self.__align == 'right':
x = -self.__widths[line]
elif self.__align == 'center':
x = -0.5*self.__widths[line]
starts.append(self.__frame.frame_to_world(vector(x,0,0)))
return starts
def set_pos(self, pos):
self.__frame.pos = vector(pos)
def get_pos(self):
return self.__frame.pos
def set_x(self, x):
self.__frame.pos.x = x
def get_x(self):
return self.__frame.pos.x
def set_y(self, y):
self.__frame.pos.y = y
def get_y(self):
return self.__frame.pos.y
def set_z(self, z):
self.__frame.pos.z = z
def get_z(self):
return self.__frame.pos.z
def set_material(self, material):
for line in range(len(self.lines)):
self.front[line].material = self.back[line].material = self.__material = material
if self.__depth.mag > 0: # may not have made sides (depth = 0)
self.sides[line].material = material
def get_material(self):
return self.__material
def set_color(self, color):
for line in range(len(self.lines)):
self.front[line].color = self.back[line].color = self.__color = color
if self.__depth.mag > 0: # may not have made sides (depth = 0)
self.sides[line].color = color
def get_color(self):
return self.__color
def set_red(self, red):
self.__color = (red, self.__color[1], self.__color[2])
self.set_color(self.__color)
def get_red(self):
return self.__color[0]
def set_green(self, green):
self.__color = (self.__color[0], green, self.__color[2])
self.set_color(self.__color)
def get_green(self):
return self.__color[1]
def set_blue(self, blue):
self.__color = (self.__color[0], self.__color[1], blue)
self.set_color(self.__color)
def get_blue(self):
return self.__color[2]
def set_axis(self, axis):
self.__frame.axis = vector(axis).norm()
def get_axis(self):
return self.__frame.axis.norm()
def set_depth(self, depth):
olddepth = self.__depth
try:
if len(depth) == 3:
self.__depth = vector(depth)
except:
self.__depth = vector(0,0,depth)
if (self.__depth-olddepth).mag == 0: return
try:
self.sides # sides does not exist if nothing but depth = 0 in the past
self.alignText()
if olddepth.z < 0:
olddepth = -olddepth
shift = self.__depth
if shift.z < 0:
shift = -shift
for line in range(len(self.lines)):
self.sides[line].pos[1::6] += shift - olddepth
self.sides[line].pos[4::6] += shift - olddepth
self.sides[line].pos[5::6] += shift - olddepth
except:
self.createText()
def get_depth(self):
return self.__depth
def set_display(self, disp):
self.__display = self.__frame.display = disp
def get_display(self):
return self.__display
def set_frame(self, fr):
self.__frame = fr
def get_frame(self):
return self.__frame
def set_up(self, up):
self.__frame.up = up
def get_up(self):
return self.__frame.up
def set_visible(self, visible):
self.__frame.visible = self.__visible = visible
def get_visible(self):
return self.__visible
pos = property( get_pos, set_pos, None)
x = property( get_x, set_x, None)
y = property( get_y, set_y, None)
z = property( get_z, set_z, None)
text = property( get_text, set_text, None)
align = property( get_align, set_align, None)
font = property( get_font, set_font, None)
height = property( get_height, set_height, None)
color = property( get_color, set_color, None)
material = property( get_material, set_material, None)
red = property( get_red, set_red, None)
green = property( get_green, set_green, None)
blue = property( get_blue, set_blue, None)
axis = property( get_axis, set_axis, None)
depth = property( get_depth, set_depth, None)
display = property( get_display, set_display, None)
frame = property( get_frame, set_frame, None)
up = property( get_up, set_up, None)
visible = property( get_visible, set_visible, None)
width = property( get_width, set_width, None)
widths = property( get_widths, set_widths, None)
descent = property( get_descent, set_descent, None)
vertical_spacing = property( get_vertical_spacing, set_vertical_spacing, None)
upper_left = property( get_upper_left, set_upper_left, None)
upper_right = property( get_upper_right, set_upper_right, None)
lower_left = property( get_lower_left, set_lower_left, None)
lower_right = property( get_lower_right, set_lower_right, None)
start = property( get_start, set_start, None)
starts = property( get_starts, set_starts, None)
spacing = property( get_spacing, set_spacing, None)
|