## simple yapCAD framework for openGL drawing using pyglet
## package
## Copyright (c) 2020 Richard W. DeVaul
## Copyright (c) 2020 yapCAD contributors
## All rights reserved
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import math
import pyglet
import pyglet.font as font
import pyglet.gl as gl
import pyglet.graphics as graphics
from yapcad.geom import *
from yapcad.geom3d import *
import yapcad.drawable as drawable
## openGL utility functions
[docs]
def vec(*args):
return (gl.GLfloat * len(args))(*args)
## HTML document, instructions for user interaction
yapCAD_legend="""
<font face="OpenSans, Geneva, sans-serif" size="5" color="#f0f0ff"><b>yapCAD</b><br></font>
<font face="Verdana, Geneva, sans-serif" size="3" color="white"><b>3D drawing viewer</b></font><br>
<font face="Verdana, Geneva, sans-serif" size="1" color="white">
<br>
<b>up-arrow</b>: zoom in<br>
<b>down-arrow</b>: zoom out<br>
<b>l</b>: toggle lighting mode<br>
<b>p</b>: toggle ground plane<br>
<b>left-mouse drag</b>: rotate view<br>
<b>m</b>: toggle display of this message<br>
<b>ESC</b>: exit viewer<br>
</font>
"""
[docs]
class Material():
"""
Representation of an OpenGL-style material
"""
def __repr__(self):
return f"Material(ambient={self.ambient}, diffuse={self.diffuse}, specular={self.specular}, emission={self.emission}, shininess={self.shininess}, desc=\"{self.desc}\")"
def __checkargs(self,v):
if ( not (len(v) == 3 or len(v) == 4) or
len(list(filter(lambda x: not isinstance(x,(int,float)),v))) > 0 or
len(list(filter(lambda x: x < 0.0 or x > 1.0,v))) > 0):
raise ValueError('bad arguments to property setter')
if len(v) == 3:
return [v[0],v[1],v[2],1.0]
else:
return v
def __init__(self,**kwargs):
self.__ambient = vec(0.2, 0.2, 0.2, 1.0)
self.__diffuse = vec(0.5, 0.5, 0.5, 1.0)
self.__specular = vec(0.5, 0.5, 0.5, 1.0)
self.__emission = vec(0.0, 0.0, 0.0, 1.0)
self.__shininess = 50
self.__desc = ""
if 'ambient' in kwargs:
self.ambient = kwargs['ambient']
if 'diffuse' in kwargs:
self.diffuse = kwargs['diffuse']
if 'specular' in kwargs:
self.specular = kwargs['specular']
if 'emission' in kwargs:
self.emission = kwargs['emission']
if 'shininess' in kwargs:
self.shininess = kwargs['shininess']
if 'desc' in kwargs:
self.desc = kwargs['desc']
@property
def ambient(self):
return list(self.__ambient)
@ambient.setter
def ambient(self,*v):
if len(v) == 1:
v = v[0]
v = self.__checkargs(v)
self.__ambient = vec(*v)
@property
def diffuse(self):
return list(self.__diffuse)
@diffuse.setter
def diffuse(self,*v):
if len(v) == 1:
v = v[0]
v = self.__checkargs(v)
self.__diffuse = vec(*v)
@property
def specular(self):
return list(self.__specular)
@specular.setter
def specular(self,*v):
if len(v) == 1:
v = v[0]
v = self.__checkargs(v)
self.__specular = vec(*v)
@property
def emission(self):
return list(self.__emission)
@emission.setter
def emission(self,*v):
if len(v) == 1:
v = v[0]
v = self.__checkargs(v)
self.__emission = vec(*v)
@property
def shininess(self):
return self.__shininess
@shininess.setter
def shininess(self,v):
if not isinstance(v,(int,float)) or v < 0 or v > 128:
raise ValueError('bad shininess')
self.__shininess=v
@property
def desc(self):
return self.__desc
@desc.setter
def desc(self,v):
if not isinstance(v,str):
raise ValueError('bad description')
self.__desc = v
@property
def material(self):
return pyglet.model.Material(self.desc,
self.diffuse,
self.ambient,
self.specular,
self.emission,
self.shininess)
## global materials dictionary
materials = {}
materials['default'] = Material(ambient=[0.5, 0.3, 0.2, 1.0],
diffuse = [0.6, 0.5, 0.3, 1.0],
specular = [1.0, 0.8, 0.5, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 20,
desc = "default material, dull gold")
materials['groundplane'] = Material(diffuse = [0.02, 0.02, 0.023, 1.0],
ambient = [0.01, 0.01, 0.02, 1.0],
specular = [0.05, 0.06, 0.08, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 20,
desc = "ground plane material")
## The following materials a from tables published at
## http://devernay.free.fr/cours/opengl/materials.html and
## http://www.it.hiof.no/~borres/j3d/explain/light/p-materials.html
materials['emerald'] = Material(ambient=[0.0215, 0.1745, 0.0215, 1.0],
diffuse=[0.07568, 0.61424, 0.07568, 1.0],
specular=[0.633, 0.727811, 0.633, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.6*128,
desc= "emerald")
materials['jade'] = Material(ambient=[0.135, 0.2225, 0.1575, 1.0],
diffuse=[0.54, 0.89, 0.63, 1.0],
specular=[0.316228, 0.316228, 0.316228, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.1*128)
materials['obsidian'] = Material(ambient=[0.05375, 0.05, 0.06625, 1.0],
diffuse=[0.18275, 0.17, 0.22525, 1.0],
specular=[0.332741, 0.328634, 0.346435, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.3*128)
materials['pearl'] = Material(ambient=[0.25, 0.20725, 0.20725, 1.0],
diffuse=[0.9999, 0.829, 0.829, 1.0],
specular=[0.296648, 0.296648, 0.296648, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.088*128)
materials['ruby'] = Material(ambient=[0.1745, 0.01175, 0.01175, 1.0],
diffuse=[0.61424, 0.04136, 0.04136, 1.0],
specular=[0.727811, 0.626959, 0.626959, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.6*128)
materials['turquoise'] = Material(ambient=[0.1, 0.18725, 0.1745, 1.0],
diffuse=[0.396, 0.74151, 0.69102, 1.0],
specular=[0.297254, 0.30829, 0.306678, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.1*128)
materials['brass'] = Material(ambient=[0.329412, 0.223529, 0.027451, 1.0],
diffuse=[0.780392, 0.568627, 0.113725, 1.0],
specular=[0.992157, 0.941176, 0.807843, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.21794872*128)
materials['bronze'] = Material(ambient=[0.2125, 0.1275, 0.054, 1.0],
diffuse=[0.714, 0.4284, 0.18144, 1.0],
specular=[0.393548, 0.271906, 0.166721, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.2*128)
materials['polished bronze'] = Material(
ambient =[0.25, 0.148, 0.06475, 1.0],
diffuse =[0.4, 0.2368, 0.1036, 1.0],
specular =[0.774597, 0.458561, 0.200621, 1.0],
shininess =76.8)
materials['chrome'] = Material(ambient=[0.25, 0.25, 0.25, 1.0],
diffuse=[0.4, 0.4, 0.4, 1.0],
specular=[0.774597, 0.774597, 0.774597, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.6*128)
materials['copper'] = Material(ambient=[0.19125, 0.0735, 0.0225, 1.0],
diffuse=[0.7038, 0.27048, 0.0828, 1.0],
specular=[0.256777, 0.137622, 0.086014, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.1*128)
materials['polished copper'] = Material(
ambient =[ 0.2295, 0.08825, 0.0275, 1.0 ],
diffuse =[0.5508, 0.2118, 0.066, 1.0 ],
specular =[0.580594, 0.223257, 0.0695701, 1.0 ],
shininess =51.2)
materials['gold'] = Material( ambient=[0.24725, 0.1995, 0.0745, 1.0],
diffuse=[0.75164, 0.60648, 0.22648, 1.0],
specular=[0.628281, 0.555802, 0.366065, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.4*128)
materials['polished gold'] = Material(
ambient =[ 0.24725, 0.2245, 0.0645, 1.0 ],
diffuse =[0.34615, 0.3143, 0.0903, 1.0 ],
specular =[ 0.797357, 0.723991, 0.208006, 1.0],
shininess =83.2)
materials['tin'] = Material(
ambient =[ 0.105882, 0.058824, 0.113725, 1.0 ],
diffuse =[0.427451, 0.470588, 0.541176, 1.0 ],
specular =[0.333333, 0.333333, 0.521569, 1.0 ],
shininess = 9.84615)
materials['silver'] = Material(
ambient=[0.19225, 0.19225, 0.19225, 1.0],
diffuse=[0.50754, 0.50754, 0.50754, 1.0],
specular=[0.508273, 0.508273, 0.508273, 1.0],
emission = [0.0, 0.0, 0.0, 1.0],
shininess = 0.4*128)
materials['polished silver'] = Material(
ambient =[ 0.23125, 0.23125, 0.23125, 1.0 ],
diffuse =[0.2775, 0.2775, 0.2775, 1.0 ],
specular =[0.773911, 0.773911, 0.773911, 1.0 ],
shininess =89.6)
materials['black plastic'] = Material(ambient=[0.0, 0.0, 0.0, 1.0],
diffuse=[0.01, 0.01, 0.01, 1.0],
specular=[0.50, 0.50, 0.50, 1.0],
shininess=.25*128)
materials['cyan plastic'] = Material(ambient=[0.0, 0.1, 0.06, 1.0],
diffuse=[0.0, 0.50980392, 0.50980392, 1.0],
specular=[0.50196078, 0.50196078,
0.50196078, 1.0],
shininess=.25*128)
materials['green plastic'] = Material(ambient=[0.0, 0.0, 0.0, 1.0],
diffuse=[0.1, 0.35, 0.1, 1.0],
specular=[0.45, 0.55, 0.45, 1.0],
shininess=.25*128)
materials['red plastic'] = Material(ambient=[0.0, 0.0, 0.0, 1.0],
diffuse=[0.5, 0.0, 0.0, 1.0],
specular=[0.7, 0.6, 0.6, 1.0],
emission=[0.0, 0.0, 0.0, 1.0],
shininess=.25*128)
materials['white plastic'] = Material(ambient=[0.0, 0.0, 0.0, 1.0],
diffuse=[0.55, 0.55, 0.55, 1.0],
specular=[0.70, 0.70, 0.70, 1.0],
shininess=.25*128)
materials['yellow plastic'] = Material(ambient=[0.0, 0.0, 0.0, 1.0],
diffuse=[0.5, 0.5, 0.0, 1.0],
specular=[0.60, 0.60, 0.50, 1.0],
shininess=.25*128)
materials['black rubber'] = Material(ambient=[0.02, 0.02, 0.02, 1.0],
diffuse=[0.01, 0.01, 0.01, 1.0],
specular=[0.4, 0.4, 0.4, 1.0],
emission=[0.0, 0.0, 0.0, 1.0],
shininess=.078125*128)
materials['cyan rubber'] = Material(ambient=[0.0, 0.05, 0.05, 1.0],
diffuse=[0.4, 0.5, 0.5, 1.0],
specular=[0.04, 0.7, 0.7, 1.0],
emission=[0.0, 0.0, 0.0, 1.0],
shininess=.078125*128)
materials['green rubber'] = Material(ambient=[0.0, 0.05, 0.0, 1.0],
diffuse=[0.4, 0.5, 0.4, 1.0],
specular=[0.04, 0.7, 0.04, 1.0],
shininess=.078125*128)
materials['red rubber'] = Material(ambient=[0.05, 0.0, 0.0, 1.0],
diffuse=[0.5, 0.4, 0.4, 1.0],
specular=[0.7, 0.04, 0.04, 1.0],
shininess=.078125*128)
materials['white rubber'] = Material(ambient=[0.05, 0.05, 0.05, 1.0],
diffuse=[0.5, 0.5, 0.5, 1.0],
specular=[0.7, 0.7, 0.7, 1.0],
emission=[0.0, 0.0, 0.0, 1.0],
shininess=.078125*128)
materials['yellow rubber'] = Material(ambient=[0.05, 0.05, 0.0, 1.0],
diffuse=[0.5, 0.5, 0.4, 1.0],
specular=[0.7, 0.7, 0.04, 1.0],
shininess=.078125*128)
## "throwaway" class for keeping track of object stuff
## class to provide openGL drawing functionality
[docs]
class pygletDraw(drawable.Drawable):
"""
yapCAD ``drawable`` subclass for OpenGL rendering with pyglet
"""
[docs]
def window(self):
window = []
try:
# Try and create a window with multisampling (antialiasing)
config = gl.Config(sample_buffers=1, samples=4, \
depth_size=16, double_buffer=True)
window = pyglet.window.Window(resizable=True, config=config)
except pyglet.window.NoSuchConfigException:
# Fall back to no multisampling for old hardware
window = pyglet.window.Window(resizable=True)
self.__fps_display = pyglet.window.FPSDisplay(window)
return window
[docs]
def glSetup(self):
gl.glClearColor(0.2, 0.2, 0.3, 1)
gl.glColor3f(1, 1, 1)
gl.glEnable(gl.GL_DEPTH_TEST)
gl.glEnable(gl.GL_CULL_FACE)
gl.glEnable(gl.GL_NORMALIZE)
gl.glLightfv(gl.GL_LIGHT0, gl.GL_POSITION, vec(50, 50, 10, 0))
gl.glLightfv(gl.GL_LIGHT0, gl.GL_SPECULAR, vec(5, 5, 10, 1))
gl.glLightfv(gl.GL_LIGHT0, gl.GL_DIFFUSE, vec(1, 1, 1, 1))
gl.glLightfv(gl.GL_LIGHT1, gl.GL_AMBIENT, vec(0, 0, 0, 1.0))
gl.glLightfv(gl.GL_LIGHT1, gl.GL_SPECULAR, vec(0.6, 0.6, 0.6, 1.0))
gl.glLightfv(gl.GL_LIGHT1, gl.GL_DIFFUSE, vec(0.8, 0.8, 0.8, 1))
gl.glLightfv(gl.GL_LIGHT1, gl.GL_POSITION, vec(-10.0, -20.0, 20.0, 0))
# Create a default material and Group for the Model
material = materials['default'].material
self.group = pyglet.model.MaterialGroup(material=material)
# Create a Material and Group for the ground plane
material2 = materials['groundplane'].material
self.group2 = pyglet.model.MaterialGroup(material=material2)
## utility funtion to update bounding box based on a list of points
[docs]
def upbb(self,pp,box,offset=[0,0,0,1]):
epsP = point(epsilon,epsilon,epsilon)
epsM = point(-epsilon,-epsilon,-epsilon)
for p in pp:
p = add(p,offset)
if box:
box = [ [min(box[0][0],p[0]),min(box[0][1],p[1]),min(box[0][2],p[2])],
[max(box[1][0],p[0]),max(box[1][1],p[1]),max(box[1][2],p[2])] ]
else:
box = [ add(p,epsM),add(p,epsP) ]
return box
[docs]
def addSurface(self,s,batch,group,offset,bbx):
assert s[0] == 'surface'
vert = s[1]
norm = s[2]
ind = s[3]
pp = []
ln = int(len(vert)/3)
for i in range(ln):
pp.append(vert[i*3:(i+1)*3])
bbx = self.upbb(pp,bbx,offset)
batch.add_indexed(int(len(vert)/3),
gl.GL_TRIANGLES,
group,
ind,
('v3f/static', vert),
('n3f/static', norm))
return bbx
__immediate = {}
[docs]
def makeBatches(self):
# convert geometry lists to overall bounding box and
# OpenGL-specific representations
bbx = False
# values for adding small offset to a point to make single-point bounding box
if ( self.__points == [] and
self.__lines == [] and
self.__linestrips == [] and
len(self.__objectdict) == 0 and
self.__surfaces == [] ):
raise ValueError('nothing to render')
for p in self.__points:
pp = p[0]
pc = p[1]
bbx = self.upbb(pp[1],bbx)
self.__batch1.add(int(len(pp[1])/3),gl.GL_POINTS,self.group,pp,pc)
for l in self.__lines:
ll = l[0]
lc = l[1]
pp = [list(ll[1][0:3])]
pp.append(list(ll[1][3:6]))
bbx = self.upbb(pp,bbx)
self.__batch1.add(int(len(ll[1])/3),gl.GL_LINES,self.group,ll,lc)
for l in self.__linestrips:
ll = l[0]
lc = l[1]
li = l[2]
pp = []
ln = int(len(ll[1])/3)
for i in range(ln):
pp.append(ll[1][i*3:(i+1)*3])
bbx =self.upbb(pp,bbx)
self.__batch1.add_indexed(int(len(ll[1])/3),
gl.GL_LINES,
self.group,
li,ll,lc)
def lineSurface(s,obj):
assert s[0] == 'surface'
vert = s[1]
ind = s[3]
drawn = set() # Use set for O(1) lookup instead of O(n) list
for j in range(0,len(ind),3):
i = ind[j:(j+3)]
l1 = i[0],i[1]
if l1[0] > l1[1]:
l1 = i[1],i[0]
l2 = i[1],i[2]
if l2[0] > l2[1]:
l2 = i[2],i[1]
l3 = i[2],i[0]
if l3[0] > l3[1]:
l3 = i[0],i[2]
if l1 not in drawn:
self.draw_line(vert[l1[0]*3:l1[0]*3+3],
vert[l1[1]*3:l1[1]*3+3],
entity=obj)
drawn.add(l1)
if l2 not in drawn:
self.draw_line(vert[l2[0]*3:l2[0]*3+3],
vert[l2[1]*3:l2[1]*3+3],
entity=obj)
drawn.add(l2)
if l3 not in drawn:
self.draw_line(vert[l3[0]*3:l3[0]*3+3],
vert[l3[1]*3:l3[1]*3+3],
entity=obj)
drawn.add(l3)
for l in obj.lines:
ll = l[0]
lc = l[1]
obj.linebatch.add(2,gl.GL_LINES,self.group,ll,lc)
for o in self.__objectdict.values():
for s in o.surfaces:
bbx = self.addSurface(s,o.batch,o.group,
point(o.x,o.y,o.z),bbx)
lineSurface(s,o)
for l in o.lines:
ll = l[0]
lc = l[1]
o.linebatch.add(2,gl.GL_LINES,self.group,ll,lc)
for s in self.__surfaces:
bbx = self.addSurface(s,self.__batch2,self.group,
point(0,0,0),bbx)
## Create a ground plane
self.__batch3.add_indexed(4,
gl.GL_TRIANGLES,
self.group2,
[0,1,2,2,3,0],
('v3f/static',[bbx[0][0]*1.2,bbx[0][1]*1.2,bbx[0][2]-0.1,
bbx[1][0]*1.2,bbx[0][1]*1.2,bbx[0][2]-0.1,
bbx[1][0]*1.2,bbx[1][1]*1.2,bbx[0][2]-0.1,
bbx[0][0]*1.2,bbx[1][1]*1.2,bbx[0][2]-0.1]),
('n3f/static',[0,0,1]*4))
self.__bbox = bbx
rnge = sub(self.__bbox[1],self.__bbox[0])
mdim = max(rnge)
mdim = max(mdim,10)
# print("scene bounding box: ",vstr(bbx))
def __init__(self):
super().__init__()
# these layers added for compatibility with ezdxf_drawable
# default layer list. For now, selecting a layer has
# no effect on drawing
self.layerlist = [False, '0','PATHS','DRILLS','DOCUMENTATION']
def on_key_press(symbol,modifiers):
handled = False
if symbol == pyglet.window.key.UP:
handled = True
self.__cameradist *= 1.1
if self.__cameradist > self.__maxcameradist:
self.__cameradist = self.__maxcameradist
elif symbol == pyglet.window.key.DOWN:
handled = True
self.__cameradist *= .90909090
if self.__cameradist < self.__mincameradist:
self.__cameradist = self.__mincameradist
elif symbol == pyglet.window.key.RETURN:
handled = True
self.__cameradist = self.camerastartdist
self.__rx = 0
self.__ry = 0
elif symbol == pyglet.window.key.P:
handled = True
self.__drawground = not self.__drawground
elif symbol == pyglet.window.key.M:
handled = True
self.__legend = not self.__legend
elif symbol == pyglet.window.key.L:
handled = True
if not (self.__light0 or self.__light1):
self.__light0 = True
gl.glEnable(gl.GL_LIGHT0)
gl.glDisable(gl.GL_LIGHT1)
elif self.__light0 and not self.__light1:
self.__light1 = True
gl.glEnable(gl.GL_LIGHT0)
gl.glEnable(gl.GL_LIGHT1)
elif self.__light0 and self.__light1:
self.__light0 = self.__light1 = False
if handled:
return pyglet.event.EVENT_HANDLED
else:
return
self.__window = self.window()
self.__window.push_handlers(on_key_press)
self.__window.projection = pyglet.window.Projection3D(zfar=1000.0)
self.glSetup()
self.__center= point(0,0)
self.__magnify = 0.08
self.__arcres = 5
self.__cameradist = self.camerastartdist = 100.0
self.__maxcameradist = 900.0
self.__mincameradist = 10.0
self.__light0 = True
self.__light1 = False
self.__legend = True
self.__drawground = True
self.__rx = 0
self.__ry = 0
self.__points= []
self.__lines= []
self.__linestrips=[]
self.__surfaces=[]
self.__labels = []
self.__batch1 = graphics.Batch() # use for lines, points, etc.
self.__batch2 = graphics.Batch() # default, use for surfaces
self.__batch3 = graphics.Batch() # use for environmental features, e.g. ground plane
self.__objectdict = {} # use for animated objects
self.__bbox = False
@self.__window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
self.__rx += dx
self.__ry -= dy
return pyglet.event.EVENT_HANDLED
@self.__window.event
def on_draw():
self.__window.clear()
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
self.__window.projection = pyglet.window.Projection3D(zfar=1000.0)
# gl.glEnable(gl.GL_BLEND)
gl.glColor3f(1., 1., 1.)
#gl.glMatrixMode(gl.GL_MODELVIEW)
gl.glLoadIdentity()
if not self.__bbox:
self.__bbox =[point(-1,-1,-1),point(1,1,1)]
cent = scale3(add(self.__bbox[0],self.__bbox[1]),0.5)
rnge = sub(self.__bbox[1],self.__bbox[0])
mdim = max(rnge)
mdim = max(mdim,10)
gl.glTranslatef(-cent[0],-cent[1],-cent[2]-1*self.__cameradist)
#gl.glTranslatef(0,0,-1*self.__cameradist)
gl.glRotatef(self.__ry%360.0, 1, 0, 0)
gl.glRotatef(self.__rx%360.0, 0, 1, 0)
#draw "ground plane"
if self.__drawground:
gl.glEnable(gl.GL_LIGHTING)
gl.glEnable(gl.GL_LIGHT0)
self.__batch3.draw()
gl.glDisable(gl.GL_LIGHTING)
self.__batch1.draw()
if self.__light0 or self.__light1:
gl.glEnable(gl.GL_LIGHTING)
self.__batch2.draw()
# execute any immediate-mode registered rendering functions
for f in self.__immediate.values():
f()
for obj in self.__objectdict.values():
gl.glMatrixMode(gl.GL_MODELVIEW)
gl.glPushMatrix()
gl.glTranslatef(obj.x,obj.y,obj.z)
gl.glRotatef(obj.rx, 1, 0, 0)
gl.glRotatef(obj.ry, 0, 1, 0)
gl.glRotatef(obj.rz, 0, 0, 1)
if obj.lighting and (self.__light0 or self.__light1):
gl.glEnable(gl.GL_LIGHTING)
obj.batch.draw()
else:
gl.glDisable(gl.GL_LIGHTING)
obj.linebatch.draw()
gl.glPopMatrix()
#gl.glDisable(gl.GL_BLEND)
gl.glDisable(gl.GL_LIGHTING)
gl.glColor3f(1., 1., 1.)
gl.glScalef(0.05, 0.05, 0.05)
for label in self.__labels:
gl.glPushMatrix()
gl.glTranslatef(0,0,label[1])
label[0].draw()
gl.glPopMatrix()
if self.__legend:
gl.glMatrixMode(gl.GL_MODELVIEW)
gl.glPushMatrix()
gl.glLoadIdentity()
gl.glMatrixMode(gl.GL_PROJECTION)
gl.glPushMatrix()
gl.glLoadIdentity()
self.__window.projection = pyglet.window.Projection2D()
label = pyglet.text.HTMLLabel(yapCAD_legend,
x=10, y=30,
anchor_x='left',anchor_y='bottom',
width=400,
multiline=True)
label.draw()
self.__fps_display.draw()
gl.glMatrixMode(gl.GL_PROJECTION)
gl.glPopMatrix()
gl.glMatrixMode(gl.GL_MODELVIEW)
gl.glPopMatrix()
return pyglet.event.EVENT_HANDLED
def __repr__(self):
return 'an instance of pygletDraw'
## properties
@property
def magnify(self):
return self.__magnify
def _set_magnify(self,mag):
self.__magnify=mag
@magnify.setter
def magnify(self,mag=False):
if not isinstance(mag,(int,float)):
raise ValueError('invalid magnification ' + str(mag))
if isinstance(mag,bool) and mag ==False:
mag = 1.0
elif mag < epsilon:
mag = epsilon
self._set_magnify(mag)
@property
def cameradist(self):
return self.__cameradist
def _set_cameradist(self,dist):
self.__cameradist=dist
@cameradist.setter
def cameradist(self,dist=False):
if not isinstance(dist,(int,float)):
raise ValueError('invalid camera distance ' + str(dist))
if isinstance(dist,bool) and dist ==False:
dist = 100.0
elif dist < self.__mincameradist:
dist = self.__mincameradist
self._set_cameradist(dist)
@property
def rx(self):
return self.__rx
@rx.setter
def rx(self,v):
if not isinstance(v,(int,float)):
raise ValueError('invalid camera rotation')
self.__rx = v%360.0
@property
def ry(self):
return self.__ry
@ry.setter
def ry(self,v):
if not isinstance(v,(int,float)):
raise ValueError('invalid camera rotation')
self.__ry = v%360.0
@property
def objectdict(self):
return self.__objectdict
## Overload virtual yapcad.drawable base class dawing methods
[docs]
def draw_point(self,p):
ar = self.__arcres
self.__arcres=30
super().draw_point(p)
self.__arcres=ar
[docs]
def draw_line(self,p1,p2,entity=None,c1=None,c2=None):
if not entity:
entity=self
elist = entity.__lines
else:
elist = entity.lines
color1 = []
color2 = []
if not c1:
color1 = self.thing2color(entity.linecolor,'f')
else:
color1 = self.thing2color(c1,'f')
if not c2:
color2 = self.thing2color(entity.linecolor,'f')
else:
color2 = self.thing2color(c2,'f')
elist.append([ ('v3f',(p1[0],p1[1],p1[2],
p2[0],p2[1],p2[2])),
('c3f',tuple(color1 + color2)) ])
[docs]
def draw_arc(self,p,r,start,end):
res = self.__arcres
# resolution of arc sampling in degrees
points = []
if not (start==0 and end==360):
start = start%360.0
end = end % 360.0
if end < start:
end = end + 360
theta = start*pi2/360.0
points.append(add(p,[math.cos(theta)*r,math.sin(theta)*r,0.0,1.0]))
for a in range(round(start),round(end),res):
theta = a*pi2/360.0
pp = [math.cos(theta)*r,math.sin(theta)*r,0.0,1.0]
pp = add(pp,p)
points.append(pp)
theta = end*pi2/360.0
points.append(add(p,[math.cos(theta)*r,math.sin(theta)*r,0.0,1.0]))
self.draw_linestrip(points)
[docs]
def draw_text(self,text,location,
align='left',
attr={'name': 'Times New Roman',
'size': 12}):
name = 'Times New Roman'
if 'font_name' in attr:
name = attr['font_name']
bold = False
if 'bold' in attr:
bold = attr['bold']
italic = False
if 'italic' in attr:
italic = attr['italic']
underline = False
if 'underline' in attr:
underline = attr['underline']
size = 12
if 'size' in attr:
size = attr['size']
elif 'height' in attr:
size *= attr['height']
anchor_x='left'
if 'anchor_x' in attr:
anchor_x = attr['anchor_x']
anchor_y='center'
if 'anchor_y' in attr:
anchor_y = attr['anchor_y']
col = []
if 'color' in attr:
col = attr['color']
elif self.linecolor:
col = self.thing2color(self.linecolor,'b')
# print ("color: ",col)
else:
col = [255,255,255]
color = tuple(col + [255])
x = location[0]
y = location[1]
self.__labels.append([pyglet.text.Label(text,
font_name=name,
font_size=size*self.__magnify,
x=x*20.0, #fudge factor to make font rendering look OK
y=y*20.0,
align=align.lower(),
color=color,
bold=bold,
italic=italic,
#underline=underline,
anchor_x=anchor_x,
anchor_y=anchor_y),
location[2]*20.0])
## OpenGL-specific drawing methods
[docs]
def draw_linestrip(self,points):
# we simulate a linestrip useing GL_LINES and indexed drawing.
# This prevents extra lines
p2 = []
i2 = []
color = self.thing2color(self.linecolor,'f')
for p in points:
p2 = p2 + p[0:3]
for i in range(1,len(points)):
i2 = i2 + [i-1, i]
c2 = color * int(len(p2)/3)
self.__linestrips.append([ ('v3f', tuple(p2)),\
('c3f',tuple(c2)),
tuple(i2) ])
[docs]
def make_object(self,name,**kwargs):
"""
Create a new three-dmensional object with specified
material properties and 6DOF in world-space that can be
animated
"""
if name in self.__objectdict.keys():
raise ValueError('duplicate object key')
obj = GeomObject()
if 'lighting' in kwargs:
if kwargs['lighting']:
obj.lighting = True
else:
obj.lighting=False
if 'position' in kwargs:
x = kwargs['position']
if not ispoint(x):
raise ValueError('bad position')
obj.x = x[0]
obj.y = x[1]
obj.z = x[2]
else:
obj.x = 0
obj.y = 0
obj.z = 0
if 'rotation' in kwargs:
r = kwargs['rotation']
obj.rx = r[0]
obj.ry = r[1]
obj.rz = r[2]
else:
obj.rx = 0
obj.ry = 0
obj.rz = 0
if 'material' in kwargs:
mat = kwargs['material']
if mat in materials:
obj.material = materials[mat]
m = obj.material.material
obj.group = pyglet.model.MaterialGroup(material=m)
else:
raise ValueError('unknown material')
else:
obj.material=materials['default']
obj.group = self.group
if 'animate' in kwargs:
pyglet.clock.schedule_interval(kwargs['animate'],1/60)
if 'linecolor' in kwargs:
obj.linecolor = kwargs['linecolor']
else:
obj.linecolor = 'gray'
obj.batch = graphics.Batch()
obj.linebatch = graphics.Batch()
obj.surfaces = []
obj.lines=[]
self.__objectdict[name] = obj
[docs]
def draw_surface(self,points,normals=None,faces=None,name=None):
"""
render a surface, either as part of default scene geometry
(if ``name`` is not specified) or as a part of a named object.
"""
if not (normals or faces):
if not issurface(points):
raise ValueError('bad surface passed to draw_surface')
normals = points[2]
faces = points[3]
points = points[1]
vrts = []
nrms= []
inds = []
for p in points:
vrts+=p[0:3]
for n in normals:
nrms+=n[0:3]
for f in faces:
inds+=f[0:3]
if not name:
self.__surfaces.append(['surface',vrts,nrms,inds])
else:
obj = self.__objectdict[name]
obj.surfaces.append(['surface',vrts,nrms,inds])
[docs]
def draw_solid(self,solid,name=None):
"""
Render a solid, either as part of the secene geometry
"""
if not issolid(solid):
raise ValueError('bad solid passed to draw_solid')
for surface in solid[1]:
self.draw_surface(surface,name=name)
## override base-class draw method
[docs]
def draw(self,x,name=None):
if issolid(x):
self.draw_solid(x,name=name)
elif issurface(x):
self.draw_surface(x,name=name)
else:
super().draw(x)
## override base-class virtual display method
[docs]
def display(self):
self.makeBatches()
pyglet.app.run()