"""
Author: Kirby Urner, 4D Solutions, Sept. 15 2005
Last modified: March 7, 2015
Version #: 1.7
(c) MIT Licence
Developed for Bridges Conference to add graphic to paper
Prototype shows two of the six plastic panels held in
place by spokes. In the final assembly, spokes are replaced
by chains in tension hooked to the six tetrahedron ribs on one
end, and to brass fixture inserts in the six panel centers.
Other materials and tension schemes apply.
"""
class Vector:
texture = 'T_Chrome_4D'
def __init__(self, xyz):
self.xyz = xyz
def write(self):
basic = "cylinder {<0,0,0>, <%s,%s,%s>, 0.1" % self.xyz
return "%s %s" % (basic, "texture {%s} no_shadow }" % self.texture)
def __repr__(self):
return "Vector <%s, %s, %s>" % self.xyz
def __mul__(self, scalar):
xyz = tuple([scalar * i for i in self.xyz])
return Vector(xyz)
def __add__(self, other):
xyz = tuple([ i+j for i,j in zip(self.xyz, other.xyz)])
return Vector(xyz)
def _get_xyz(self):
return '<%s, %s, %s>' % self.xyz
def _get_length(self):
return pow(sum([i**2 for i in xyz]), 0.5)
coords = property(_get_xyz)
length = property(_get_length)
class Edge(object):
texture = 'T_Chrome_4D'
def __init__(self, v0, v1):
self.v0 = v0
self.v1 = v1
def __repr__(self):
return "Edge %s, %s" % (self.v0.coords, self.v1.coords)
def write(self):
basic = "cylinder {%s, %s, 0.05" % (self.v0.coords, self.v1.coords)
return "%s %s" % (basic, "texture {%s} no_shadow }" % self.texture)
class Vertex(object):
texture = 'T_Chrome_4D'
radius = 0.05
def __init__(self, v0):
self.v0 = v0
def __repr__(self):
return "Sphere %s, radius = %s" % (self.v0.coords, self.radius)
def write(self):
basic = "sphere {%s, %s" % (self.v0.coords, self.radius)
return "%s %s" % (basic, "texture {%s} no_shadow }" % self.texture)
class Polyhedron:
texture = 'Red'
def __init__(self, vertsdict, faces):
self.verts = vertsdict
self.faces = faces
self.edges = self.__get_edges()
def __repr__(self):
return "Polyhedron: %s vertices, %s edges, %s faces" \
% (len(self.verts), len(self.edges), len(self.faces))
def __get_edges(self):
"""
take a list of face-tuples and distill
all the unique edges,
e.g. ((1,2,3)) => ((1,2),(2,3),(3,1))
e.g. icosahedron has 20 faces and 30 unique edges
( = cubocta 24 + tetra's 6 edges to squares per
jitterbug)
"""
uniqueset = set()
for f in self.faces:
edgetries = zip(f, f[1:]+ (f[0],))
for e in edgetries:
e = tuple(sorted(e))
uniqueset.add(e)
return tuple(uniqueset)
def write(self, name):
"""
generate the edges and vertices in POV-Ray
scene description language
"""
lines = ['#declare %s = union {' % name]
for e in self.edges:
edge = Edge( self.verts[e[0]], self.verts[e[1]] )
edge.texture = self.texture
lines.append(edge.write())
for v in self.verts.values():
sphere = Vertex(v)
sphere.texture = self.texture
lines.append(sphere.write())
lines.append("}")
lines.append("object {%s}\n" % name)
return '\n'.join(lines)
def __mul__(self, scalar):
newvectors = {}
for v in self.verts:
newvectors[v] = self.verts[v] * scalar
return Polyhedron(newvectors, self.faces)
class Header (object):
# defaults
bgcolor = "color White"
lightloc0 = "<300, 300, -1000>"
lightloc1 = "<-300, 300, -1000>"
lightcolor = "White"
@staticmethod
def write():
return """
#version 3.6; // 3.7;
global_settings{ assumed_gamma 1.0 }
#default{ finish{ ambient 0.1 diffuse 0.9 }}
#include "colors.inc"
#include "textures.inc"
#include "woods.inc"
#include "glass_old.inc"
// sun -------------------------------------------------------------
light_source{<1500,2000,-2500> color White*0.7}
light_source{<-500, 500,-2500> color Yellow*0.7}
// sky -------------------------------------------------------------
sphere{<0,0,0>,1 hollow
texture{pigment{gradient <0,1,0>
color_map{[0 color White] //<---2
[0.15 color White]
[1.0 color Blue]}}
finish {ambient 1 diffuse 0} }
scale 10000}
//==================================================================
#include "colors.inc"
#include "metals.inc"
#include "glass.inc"
"""
class Camera:
# defaults
look_at = 0
location = '<0, .1, -25>'
@staticmethod
def write():
return """
camera {
location %(location)s
look_at %(look_at)s
}
""" % Camera.__dict__
def buildicosa():
"""
Build an icosahedron from three mutually perpendicular
golden rectangles
"""
phi = (1 + pow(5,0.5))/2.0
verts = {# 2*phi x 2 rectangle in XZ
1:Vector((-phi, 0, -1)),
2:Vector((-phi, 0, 1)),
3:Vector(( phi, 0, 1)),
4:Vector(( phi, 0, -1)),
# 2*phi x 2 rectange in XY
5:Vector(( -1, phi, 0)),
6:Vector(( 1, phi, 0)),
7:Vector(( 1, -phi, 0)),
8:Vector(( -1, -phi, 0)),
# 2*phi x 2 rectange in YZ
9:Vector(( 0, 1, phi)),
10:Vector(( 0, -1, phi)),
11:Vector(( 0, -1, -phi)),
12:Vector(( 0, 1, -phi))}
faces = ((5,6,9),(5,9,2),(5,2,1),(5,1,12),(5,12,6),
(1,11,12),(11,12,4),(12,4,6),(4,6,3),(6,3,9),
(3,9,10),(9,10,2),(10,2,8),(2,8,1),(8,1,11),
(7,11,4),(7,4,3),(7,3,10),(7,10,8),(7,8,11))
return verts, faces
rt2 = pow(2, 1/2)
def build_cube():
"""
The hexahedron panels need no glue to adhere thanks to
beveling and inward pull of the six chains.
"""
verts = {# octants left of Z plane
1:Vector((-rt2, -rt2, rt2)),
2:Vector((-rt2, -rt2, -rt2)),
3:Vector((-rt2, rt2, -rt2)),
4:Vector((-rt2, rt2, rt2)),
# octants right of Z plane
5:Vector(( rt2, -rt2, rt2)),
6:Vector(( rt2, -rt2, -rt2)),
7:Vector(( rt2, rt2, -rt2)),
8:Vector(( rt2, rt2, rt2))}
faces = ((1,2,3,4), (4,8,7,3), (8,5,6,7), (1,2,6,5),
(2,3,7,6), (1,4,8,5))
return verts, faces
def build_tetrahedron():
verts = {# octants left of Z plane
2:Vector((-rt2, -rt2, -rt2)),
4:Vector((-rt2, rt2, rt2)),
# octants right of Z plane
5:Vector(( rt2, -rt2, rt2)),
7:Vector(( rt2, rt2, -rt2))}
faces = ((4,5,2),(4,5,7),(2,7,5),(2,7,4))
return verts, faces
def write_boxes(name):
defines = "#declare Face = box{{ < -{0},-{0},-{0}>, <{0},{0},-{0}>
texture{{ T_Ruby_Glass }} }}\n".format(rt2)
defines += "object{Face}\n"
defines += "object{Face rotate <0, 180, 0>}\n"
return defines
def build_spokes():
"""
to be replaced with chains, springs, or wires / ropes with turnbuckle
adjustment
"""
scalar = 0.25
verts = {
'A0' : Vector((-rt2, 0, 0)),
'B0' : Vector(( 0, -rt2, 0)),
'C0' : Vector(( 0, 0, -rt2)),
'D0' : Vector(( rt2, 0, 0)),
'E0' : Vector(( 0, rt2, 0)),
'F0' : Vector(( 0, 0, rt2)),
'A1' : Vector((-rt2, 0, 0)) * scalar,
'B1' : Vector(( 0, -rt2, 0)) * scalar,
'C1' : Vector(( 0, 0, -rt2)) * scalar,
'D1' : Vector(( rt2, 0, 0)) * scalar,
'E1' : Vector(( 0, rt2, 0)) * scalar,
'F1' : Vector(( 0, 0, rt2)) * scalar }
faces = (('A0', 'A1'), ('B0', 'B1'), ('C0', 'C1'),('D0',
'D1'),('E0','E1'),('F0','F1'))
return verts, faces
def main():
cube = Polyhedron(*build_cube())
tetra = Polyhedron(*build_tetrahedron()) * 0.25
spokes = Polyhedron(*build_spokes())
# overwriting defaults:
cube.texture = 'T_Wood33'
tetra.texture = 'T_Silver_4D'
spokes.texture = 'T_Brass_4D'
Header.bgcolor = 'Gray20'
Header.lightloc1 = '<0, 0, 0>'
Camera.location = '<2, 4, -3>'
f = open('bridges.pov','w')
print(Header.write(), file = f)
print(Camera.write(), file = f)
print(cube.write("duo_tet"), file=f) # shapes need a name
print(tetra.write("tet"), file=f) # shapes need a name
print(spokes.write("spokes"), file=f) # shapes need a name
print(write_boxes("boxes"), file=f) # shapes need a name
f.close() # just to be neat & tidy
if __name__ == '__main__':
main()