summaryrefslogtreecommitdiffstats
path: root/libdimension-python/dimension.pyx
diff options
context:
space:
mode:
Diffstat (limited to 'libdimension-python/dimension.pyx')
-rw-r--r--libdimension-python/dimension.pyx684
1 files changed, 528 insertions, 156 deletions
diff --git a/libdimension-python/dimension.pyx b/libdimension-python/dimension.pyx
index dd9ff17..fa57691 100644
--- a/libdimension-python/dimension.pyx
+++ b/libdimension-python/dimension.pyx
@@ -17,6 +17,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
#########################################################################
+"""
+Dimension: a high-performance photo-realistic 3D renderer.
+"""
+
import os
###########
@@ -24,40 +28,51 @@ import os
###########
# Make warnings fatal
-def dieOnWarnings(alwaysDie):
- dmnsn_die_on_warnings(alwaysDie)
+def die_on_warnings(always_die):
+ """Whether to treat Dimension warnings as errors."""
+ dmnsn_die_on_warnings(always_die)
##########
# Timers #
##########
cdef class Timer:
+ """A timer for Dimension tasks."""
cdef dmnsn_timer *_timer
def __init__(self):
+ """
+ Create a Timer.
+
+ Timing starts as soon as the object is created.
+ """
self._timer = dmnsn_new_timer()
def __dealloc__(self):
dmnsn_delete_timer(self._timer)
def complete(self):
+ """Stop the Timer."""
dmnsn_complete_timer(self._timer)
property real:
+ """Real (wall clock) time."""
def __get__(self):
return self._timer.real
property user:
+ """User (CPU) time."""
def __get__(self):
return self._timer.user
property system:
+ """System time."""
def __get__(self):
return self._timer.system
def __str__(self):
- return '%.2fs (user: %.2fs; system: %.2fs)' % \
+ return "%.2fs (user: %.2fs; system: %.2fs)" % \
(self._timer.real, self._timer.user, self._timer.system)
-cdef _rawTimer(dmnsn_timer *timer):
+cdef _Timer(dmnsn_timer *timer):
cdef Timer self = Timer.__new__(Timer)
self._timer = timer
DMNSN_INCREF(self._timer)
@@ -68,99 +83,121 @@ cdef _rawTimer(dmnsn_timer *timer):
############
cdef class Vector:
+ """A vector (or point or pseudovector) in 3D space."""
cdef dmnsn_vector _v
def __init__(self, *args, **kwargs):
+ """
+ Create a Vector.
+
+ Keyword arguments:
+ x -- The x coordinate
+ y -- The y coordinate
+ z -- The z coordinate
+
+ Alternatively, you can pass another Vector, the value 0, or a tuple or other
+ sequence (x, y, z).
+ """
if len(args) == 1:
- if (isinstance(args[0], Vector)):
+ if isinstance(args[0], Vector):
self._v = (<Vector>args[0])._v
- elif (isinstance(args[0], tuple)):
- self._realInit(*args[0])
- elif (args[0] == 0):
+ elif hasattr(args[0], '__iter__'): # Faster than try: ... except:
+ self._real_init(*args[0])
+ elif args[0] == 0:
self._v = dmnsn_zero
else:
- raise TypeError, 'expected a tuple or 0'
+ raise TypeError, "expected a sequence or 0"
else:
- self._realInit(*args, **kwargs)
+ self._real_init(*args, **kwargs)
- def _realInit(self, double x, double y, double z):
+ def _real_init(self, double x, double y, double z):
self._v = dmnsn_new_vector(x, y, z)
property x:
+ """The x coordinate."""
def __get__(self):
return self._v.x
property y:
+ """The y coordinate."""
def __get__(self):
return self._v.y
property z:
+ """The z coordinate."""
def __get__(self):
return self._v.z
def __pos__(self):
return self
def __neg__(self):
- return _rawVector(dmnsn_vector_negate(self._v))
+ return _Vector(dmnsn_vector_negate(self._v))
def __nonzero__(self):
return dmnsn_vector_norm(self._v) >= dmnsn_epsilon
def __add__(lhs, rhs):
- return _rawVector(dmnsn_vector_add(Vector(lhs)._v, Vector(rhs)._v))
+ return _Vector(dmnsn_vector_add(Vector(lhs)._v, Vector(rhs)._v))
def __sub__(lhs, rhs):
- return _rawVector(dmnsn_vector_sub(Vector(lhs)._v, Vector(rhs)._v))
+ return _Vector(dmnsn_vector_sub(Vector(lhs)._v, Vector(rhs)._v))
def __mul__(lhs, rhs):
- if (isinstance(lhs, Vector)):
- return _rawVector(dmnsn_vector_mul(rhs, (<Vector>lhs)._v))
+ if isinstance(lhs, Vector):
+ return _Vector(dmnsn_vector_mul(rhs, (<Vector>lhs)._v))
else:
- return _rawVector(dmnsn_vector_mul(lhs, (<Vector>rhs)._v))
+ return _Vector(dmnsn_vector_mul(lhs, (<Vector>rhs)._v))
def __truediv__(Vector lhs not None, double rhs):
- return _rawVector(dmnsn_vector_div(lhs._v, rhs))
+ return _Vector(dmnsn_vector_div(lhs._v, rhs))
def __richcmp__(lhs, rhs, int op):
equal = (Vector(lhs) - Vector(rhs)).norm() < dmnsn_epsilon
- if (op == 2): # ==
+ if op == 2: # ==
return equal
- elif (op == 3): # !=
+ elif op == 3: # !=
return not equal
else:
return NotImplemented
def norm(self):
+ """Return the magnitude of the vector."""
return dmnsn_vector_norm(self._v)
def normalized(self):
- return _rawVector(dmnsn_vector_normalized(self._v))
+ """Return the direction of the vector."""
+ return _Vector(dmnsn_vector_normalized(self._v))
def __repr__(self):
- return 'dimension.Vector(%r, %r, %r)' % (self.x, self.y, self.z)
+ return "dimension.Vector(%r, %r, %r)" % (self.x, self.y, self.z)
def __str__(self):
- return '<%s, %s, %s>' % (self.x, self.y, self.z)
+ return "<%s, %s, %s>" % (self.x, self.y, self.z)
-cdef _rawVector(dmnsn_vector v):
+cdef _Vector(dmnsn_vector v):
cdef Vector self = Vector.__new__(Vector)
self._v = v
return self
def cross(Vector lhs not None, Vector rhs not None):
- return _rawVector(dmnsn_vector_cross(lhs._v, rhs._v))
+ """Vector cross product."""
+ return _Vector(dmnsn_vector_cross(lhs._v, rhs._v))
def dot(Vector lhs not None, Vector rhs not None):
+ """Vector dot product."""
return dmnsn_vector_dot(lhs._v, rhs._v)
def proj(Vector u not None, Vector d not None):
- return _rawVector(dmnsn_vector_proj(u._v, d._v))
+ """Vector projection (of u onto d)."""
+ return _Vector(dmnsn_vector_proj(u._v, d._v))
-X = _rawVector(dmnsn_x)
-Y = _rawVector(dmnsn_y)
-Z = _rawVector(dmnsn_z)
+X = _Vector(dmnsn_x)
+Y = _Vector(dmnsn_y)
+Z = _Vector(dmnsn_z)
cdef class Matrix:
+ """An affine transformation matrix."""
cdef dmnsn_matrix _m
def __init__(self,
double a1, double a2, double a3, double a4,
double b1, double b2, double b3, double b4,
double c1, double c2, double c3, double c4):
- self._m = dmnsn_new_matrix(a1, a2, a3, a4,
- b1, b2, b3, b4,
- c1, c2, c3, c4)
+ """Create a Matrix."""
+ self._m = dmnsn_new_matrix(a1, a2, a3, a4,
+ b1, b2, b3, b4,
+ c1, c2, c3, c4)
def __nonzero__(self):
cdef double sum = 0.0
@@ -170,10 +207,10 @@ cdef class Matrix:
return sqrt(sum) >= dmnsn_epsilon
def __mul__(Matrix lhs not None, rhs):
- if (isinstance(rhs, Matrix)):
- return _rawMatrix(dmnsn_matrix_mul(lhs._m, (<Matrix>rhs)._m))
+ if isinstance(rhs, Matrix):
+ return _Matrix(dmnsn_matrix_mul(lhs._m, (<Matrix>rhs)._m))
else:
- return _rawVector(dmnsn_transform_vector(lhs._m, (<Vector>rhs)._v))
+ return _Vector(dmnsn_transform_vector(lhs._m, (<Vector>rhs)._v))
def __richcmp__(Matrix lhs not None, Matrix rhs not None, int op):
cdef double sum = 0.0
@@ -183,100 +220,155 @@ cdef class Matrix:
sum += diff*diff
equal = sqrt(sum) < dmnsn_epsilon
- if (op == 2): # ==
+ if op == 2: # ==
return equal
- elif (op == 3): # !=
+ elif op == 3: # !=
return not equal
else:
return NotImplemented
cpdef Matrix inverse(self):
- return _rawMatrix(dmnsn_matrix_inverse(self._m));
+ """Return the inverse of a matrix."""
+ return _Matrix(dmnsn_matrix_inverse(self._m));
def __repr__(self):
return \
- 'dimension.Matrix(%r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r)' % \
+ "dimension.Matrix(%r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r)" % \
(self._m.n[0][0], self._m.n[0][1], self._m.n[0][2], self._m.n[0][3],
self._m.n[1][0], self._m.n[1][1], self._m.n[1][2], self._m.n[1][3],
self._m.n[2][0], self._m.n[2][1], self._m.n[2][2], self._m.n[2][3])
def __str__(self):
return \
- '\n[%s\t%s\t%s\t%s]' \
- '\n[%s\t%s\t%s\t%s]' \
- '\n[%s\t%s\t%s\t%s]' \
- '\n[%s\t%s\t%s\t%s]' %\
+ "\n[%s\t%s\t%s\t%s]" \
+ "\n[%s\t%s\t%s\t%s]" \
+ "\n[%s\t%s\t%s\t%s]" \
+ "\n[%s\t%s\t%s\t%s]" %\
(self._m.n[0][0], self._m.n[0][1], self._m.n[0][2], self._m.n[0][3],
self._m.n[1][0], self._m.n[1][1], self._m.n[1][2], self._m.n[1][3],
self._m.n[2][0], self._m.n[2][1], self._m.n[2][2], self._m.n[2][3],
0.0, 0.0, 0.0, 1.0)
-cdef Matrix _rawMatrix(dmnsn_matrix m):
+cdef Matrix _Matrix(dmnsn_matrix m):
cdef Matrix self = Matrix.__new__(Matrix)
self._m = m
return self
def scale(*args, **kwargs):
- return _rawMatrix(dmnsn_scale_matrix(Vector(*args, **kwargs)._v))
+ """
+ Return a scale transformation.
+
+ Accepts the same arguments that Vector(...) does. The transformation scales
+ by a factor of x in the x direction, y in the y direction, and z in the z
+ direction. In particular, this means that scale(2*X) is probably a mistake,
+ as the y and z coordinates will disappear.
+
+ Alternatively, a single argument may be passed, which specifies the scaling
+ factor in every component.
+ """
+ cdef Vector s
+ try:
+ s = Vector(*args, **kwargs)
+ except:
+ s = args[0]*(X + Y + Z)
+ return _Matrix(dmnsn_scale_matrix(s._v))
def translate(*args, **kwargs):
- return _rawMatrix(dmnsn_translation_matrix(Vector(*args, **kwargs)._v))
+ """
+ Return a translation.
+
+ Accepts the same arguments that Vector(...) does.
+ """
+ return _Matrix(dmnsn_translation_matrix(Vector(*args, **kwargs)._v))
def rotate(*args, **kwargs):
+ """
+ Return a rotation.
+
+ Accepts the same arguments that Vector(...) does. theta.norm() is the left-
+ handed angle of rotation, and theta.normalized() is the axis of rotation.
+ theta is specified in degrees.
+ """
cdef Vector rad = dmnsn_radians(1.0)*Vector(*args, **kwargs)
- return _rawMatrix(dmnsn_rotation_matrix(rad._v))
-def _rawRotate(*args, **kwargs):
- return _rawMatrix(dmnsn_rotation_matrix(Vector(*args, **kwargs)._v))
+ return _Matrix(dmnsn_rotation_matrix(rad._v))
+def _Rotate(*args, **kwargs):
+ return _Matrix(dmnsn_rotation_matrix(Vector(*args, **kwargs)._v))
##########
# Colors #
##########
cdef class Color:
+ """
+ An sRGB color.
+
+ Note that 0.5*White == Color(0.5, 0.5, 0.5), which is not technically a half-
+ intensity white, due to sRGB gamma. Dimension handles the gamma correctly
+ when rendering, though.
+ """
cdef dmnsn_color _c
cdef dmnsn_color _sRGB
def __init__(self, *args, **kwargs):
+ """
+ Create a Color.
+
+ Keyword arguments:
+ red -- The red component
+ green -- The green component
+ blue -- The blue component
+ trans -- The transparency of the color, 0.0 meaning opaque (default 0.0)
+ filter -- How filtered the transparency is (default 0.0)
+
+ Alternatively, you can pass another Color, a gray intensity like 0.5, or a
+ tuple or other sequence (red, green, blue[, trans[, filter]]).
+ """
if len(args) == 1:
- if (isinstance(args[0], Color)):
+ if isinstance(args[0], Color):
self._sRGB = (<Color>args[0])._sRGB
- elif (isinstance(args[0], tuple)):
- self._realInit(*args[0])
+ elif hasattr(args[0], '__iter__'):
+ self._real_init(*args[0])
else:
self._sRGB = dmnsn_color_mul(args[0], dmnsn_white)
else:
- self._realInit(*args, **kwargs)
+ self._real_init(*args, **kwargs)
self._c = dmnsn_color_from_sRGB(self._sRGB)
- def _realInit(self, double red, double green, double blue,
- double trans = 0.0, double filter = 0.0):
+ def _real_init(self, double red, double green, double blue,
+ double trans = 0.0, double filter = 0.0):
self._sRGB = dmnsn_new_color5(red, green, blue, trans, filter)
property red:
+ """The red component."""
def __get__(self):
return self._sRGB.R
property green:
+ """The green component."""
def __get__(self):
return self._sRGB.G
property blue:
+ """The blue component."""
def __get__(self):
return self._sRGB.B
property trans:
+ """The transparency of the color."""
def __get__(self):
return self._sRGB.trans
property filter:
+ """How filtered the transparency is."""
def __get__(self):
return self._sRGB.filter
def __nonzero__(self):
+ """Return whether a color is not black."""
return not dmnsn_color_is_black(self._c)
def __add__(lhs, rhs):
- return _rawsRGBColor(dmnsn_color_add(Color(lhs)._sRGB, Color(rhs)._sRGB))
+ return _sRGBColor(dmnsn_color_add(Color(lhs)._sRGB, Color(rhs)._sRGB))
def __mul__(lhs, rhs):
- if (isinstance(lhs, Color)):
- return _rawsRGBColor(dmnsn_color_mul(rhs, (<Color>lhs)._sRGB))
+ if isinstance(lhs, Color):
+ return _sRGBColor(dmnsn_color_mul(rhs, (<Color>lhs)._sRGB))
else:
- return _rawsRGBColor(dmnsn_color_mul(lhs, (<Color>rhs)._sRGB))
+ return _sRGBColor(dmnsn_color_mul(lhs, (<Color>rhs)._sRGB))
def __richcmp__(lhs, rhs, int op):
cdef clhs = Color(lhs)
@@ -290,81 +382,95 @@ cdef class Color:
cdef double sum = rdiff*rdiff + gdiff*gdiff + bdiff*bdiff \
+ tdiff*tdiff + fdiff*fdiff
equal = sqrt(sum) < dmnsn_epsilon
- if (op == 2): # ==
+ if op == 2: # ==
return equal
- elif (op == 3): # !=
+ elif op == 3: # !=
return not equal
else:
return NotImplemented
def __repr__(self):
- return 'dimension.Color(%r, %r, %r, %r, %r)' % \
+ return "dimension.Color(%r, %r, %r, %r, %r)" % \
(self.red, self.green, self.blue, self.trans, self.filter)
def __str__(self):
- if (self.trans >= dmnsn_epsilon):
- return '<red = %s, green = %s, blue = %s, trans = %s, filter = %s>' % \
+ if self.trans >= dmnsn_epsilon:
+ return "<red = %s, green = %s, blue = %s, trans = %s, filter = %s>" % \
(self.red, self.green, self.blue, self.trans, self.filter)
else:
- return '<red = %s, green = %s, blue = %s>' % \
+ return "<red = %s, green = %s, blue = %s>" % \
(self.red, self.green, self.blue)
-cdef _rawsRGBColor(dmnsn_color sRGB):
+cdef _sRGBColor(dmnsn_color sRGB):
cdef Color self = Color.__new__(Color)
self._sRGB = sRGB
self._c = dmnsn_color_from_sRGB(sRGB)
return self
-cdef _rawColor(dmnsn_color c):
+cdef _Color(dmnsn_color c):
cdef Color self = Color.__new__(Color)
self._c = c
self._sRGB = dmnsn_color_to_sRGB(c)
return self
-Black = _rawColor(dmnsn_black)
-White = _rawColor(dmnsn_white)
-Clear = _rawColor(dmnsn_clear)
-Red = _rawColor(dmnsn_red)
-Green = _rawColor(dmnsn_green)
-Blue = _rawColor(dmnsn_blue)
-Magenta = _rawColor(dmnsn_magenta)
-Orange = _rawColor(dmnsn_orange)
-Yellow = _rawColor(dmnsn_yellow)
-Cyan = _rawColor(dmnsn_cyan)
+Black = _Color(dmnsn_black)
+White = _Color(dmnsn_white)
+Clear = _Color(dmnsn_clear)
+Red = _Color(dmnsn_red)
+Green = _Color(dmnsn_green)
+Blue = _Color(dmnsn_blue)
+Magenta = _Color(dmnsn_magenta)
+Orange = _Color(dmnsn_orange)
+Yellow = _Color(dmnsn_yellow)
+Cyan = _Color(dmnsn_cyan)
############
# Canvases #
############
cdef class Canvas:
+ """A rendering target."""
cdef dmnsn_canvas *_canvas
- def __cinit__(self, size_t width, size_t height):
+ def __init__(self, size_t width, size_t height):
+ """
+ Create a Canvas.
+
+ Keyword arguments:
+ width -- the width of the canvas
+ height -- the height of the canvas
+ """
self._canvas = dmnsn_new_canvas(width, height)
def __dealloc__(self):
dmnsn_delete_canvas(self._canvas)
property width:
+ """The width of the canvas."""
def __get__(self):
return self._canvas.width
property height:
+ """The height of the canvas."""
def __get__(self):
return self._canvas.height
- def optimizePNG(self):
+ def optimize_PNG(self):
+ """Optimize a canvas for PNG output."""
if dmnsn_png_optimize_canvas(self._canvas) != 0:
raise OSError(errno, os.strerror(errno))
- def optimizeGL(self):
+ def optimize_GL(self):
+ """Optimize a canvas for OpenGL output."""
if dmnsn_gl_optimize_canvas(self._canvas) != 0:
raise OSError(errno, os.strerror(errno))
def clear(self, c):
+ """Clear a canvas with a solid color."""
dmnsn_clear_canvas(self._canvas, Color(c)._c)
- def writePNG(self, str path not None):
- bpath = path.encode('UTF-8')
+ def write_PNG(self, path):
+ """Export the canvas as a PNG file."""
+ bpath = path.encode("UTF-8")
cdef char *cpath = bpath
cdef FILE *file = fopen(cpath, "wb")
if file == NULL:
@@ -373,7 +479,8 @@ cdef class Canvas:
if dmnsn_png_write_canvas(self._canvas, file) != 0:
raise OSError(errno, os.strerror(errno))
- def drawGL(self):
+ def draw_GL(self):
+ """Export the canvas to the current OpenGL context."""
if dmnsn_gl_write_canvas(self._canvas) != 0:
raise OSError(errno, os.strerror(errno))
@@ -382,6 +489,7 @@ cdef class Canvas:
############
cdef class Pattern:
+ """A function which maps points in 3D space to scalar values."""
cdef dmnsn_pattern *_pattern
def __cinit__(self):
@@ -391,19 +499,28 @@ cdef class Pattern:
dmnsn_delete_pattern(self._pattern)
def transform(self, Matrix trans not None):
+ """Transform a pattern."""
if self._pattern == NULL:
- raise TypeError('attempt to transform base Pattern')
+ raise TypeError("attempt to transform base Pattern")
self._pattern.trans = dmnsn_matrix_mul(trans._m, self._pattern.trans)
return self
cdef class Checker(Pattern):
+ """A checkerboard pattern."""
def __init__(self):
self._pattern = dmnsn_new_checker_pattern()
Pattern.__init__(self)
cdef class Gradient(Pattern):
+ """A gradient pattern."""
def __init__(self, orientation):
+ """
+ Create a gradient pattern.
+
+ Keyword arguments:
+ orientation -- The direction of the linear gradient.
+ """
self._pattern = dmnsn_new_gradient_pattern(Vector(orientation)._v)
Pattern.__init__(self)
@@ -412,14 +529,21 @@ cdef class Gradient(Pattern):
############
cdef class Pigment:
+ """Object surface coloring."""
cdef dmnsn_pigment *_pigment
def __cinit__(self):
self._pigment = NULL
def __init__(self, arg = None):
+ """
+ Create a Pigment.
+
+ With an arguement, create a solid pigment of that color. Otherwise, create
+ a base Pigment.
+ """
if arg is not None:
- if (isinstance(arg, Pigment)):
+ if isinstance(arg, Pigment):
self._pigment = (<Pigment>arg)._pigment
DMNSN_INCREF(self._pigment)
else:
@@ -429,22 +553,34 @@ cdef class Pigment:
dmnsn_delete_pigment(self._pigment)
def transform(self, Matrix trans not None):
+ """Transform a pigment."""
if self._pigment == NULL:
- raise TypeError('attempt to transform base Pigment')
+ raise TypeError("attempt to transform base Pigment")
self._pigment.trans = dmnsn_matrix_mul(trans._m, self._pigment.trans)
return self
-cdef _rawPigment(dmnsn_pigment *pigment):
+cdef _Pigment(dmnsn_pigment *pigment):
cdef Pigment self = Pigment.__new__(Pigment)
self._pigment = pigment
DMNSN_INCREF(self._pigment)
return self
cdef class ColorMap(Pigment):
+ """A color map"""
def __init__(self, Pattern pattern not None, map, bool sRGB not None = True):
+ """
+ Create a ColorMap.
+
+ Keyword arguments:
+ pattern -- the pattern to use for the mapping
+ map -- a dictionary of the form { val1: color1, val2: color2, ... },
+ or a list of the form [color1, color2, ...]
+ sRGB -- whether the gradients should be in sRGB or linear space
+ (default True)
+ """
cdef dmnsn_map *color_map = dmnsn_new_color_map()
- if hasattr(map, 'items'):
+ if hasattr(map, "items"):
for i, color in map.items():
dmnsn_add_map_entry(color_map, i, &Color(color)._c)
else:
@@ -463,21 +599,32 @@ cdef class ColorMap(Pigment):
Pigment.__init__(self)
cdef class PigmentMap(Pigment):
+ """A pigment map."""
def __init__(self, Pattern pattern not None, map, bool sRGB not None = True):
+ """
+ Create a PigmentMap.
+
+ Keyword arguments:
+ pattern -- the pattern to use for the mapping
+ map -- a dictionary of the form { val1: color1, val2: pigment2, ... },
+ or a list of the form [color1, pigment2, ...]
+ sRGB -- whether the gradients should be in sRGB or linear space
+ (default True)
+ """
cdef dmnsn_map *pigment_map = dmnsn_new_pigment_map()
- cdef dmnsn_pigment *realPigment
- if hasattr(map, 'items'):
+ cdef dmnsn_pigment *real_pigment
+ if hasattr(map, "items"):
for i, pigment in map.items():
pigment = Pigment(pigment)
- realPigment = (<Pigment>pigment)._pigment
- DMNSN_INCREF(realPigment)
- dmnsn_add_map_entry(pigment_map, i, &realPigment)
+ real_pigment = (<Pigment>pigment)._pigment
+ DMNSN_INCREF(real_pigment)
+ dmnsn_add_map_entry(pigment_map, i, &real_pigment)
else:
for i, pigment in enumerate(map):
pigment = Pigment(pigment)
- realPigment = (<Pigment>pigment)._pigment
- DMNSN_INCREF(realPigment)
- dmnsn_add_map_entry(pigment_map, i/len(map), &realPigment)
+ real_pigment = (<Pigment>pigment)._pigment
+ DMNSN_INCREF(real_pigment)
+ dmnsn_add_map_entry(pigment_map, i/len(map), &real_pigment)
cdef dmnsn_pigment_map_flags flags
if sRGB:
@@ -495,6 +642,7 @@ cdef class PigmentMap(Pigment):
############
cdef class Finish:
+ """Object surface qualities."""
cdef dmnsn_finish _finish
def __cinit__(self):
@@ -504,33 +652,66 @@ cdef class Finish:
dmnsn_delete_finish(self._finish)
def __add__(Finish lhs not None, Finish rhs not None):
+ """
+ Combine two finishes.
+
+ In lhs + rhs, the attributes of rhs override those of lhs if any conflict;
+ thus, Ambient(0.1) + Ambient(0.2) is the same as Ambient(0.2)
+ """
cdef Finish ret = Finish()
dmnsn_finish_cascade(&lhs._finish, &ret._finish)
dmnsn_finish_cascade(&rhs._finish, &ret._finish) # rhs gets priority
return ret
-cdef _rawFinish(dmnsn_finish finish):
+cdef _Finish(dmnsn_finish finish):
cdef Finish self = Finish.__new__(Finish)
self._finish = finish
dmnsn_finish_incref(&self._finish)
return self
cdef class Ambient(Finish):
+ """Ambient light reflected."""
def __init__(self, color):
+ """
+ Create an Ambient finish.
+
+ Keyword arguments:
+ color -- the color and intensity of the ambient light
+ """
self._finish.ambient = dmnsn_new_basic_ambient(Color(color)._c)
cdef class Diffuse(Finish):
+ """Lambertian diffuse reflection."""
def __init__(self, double diffuse):
+ """
+ Create a Diffuse finish.
+
+ Keyword arguments:
+ diffuse -- the intensity of the diffuse reflection
+ """
cdef dmnsn_color gray = dmnsn_color_mul(diffuse, dmnsn_white)
diffuse = dmnsn_color_intensity(dmnsn_color_from_sRGB(gray))
self._finish.diffuse = dmnsn_new_lambertian(diffuse)
cdef class Phong(Finish):
+ """Phong specular highlight."""
def __init__(self, double strength, double size = 40.0):
+ """
+ Create a Phong highlight.
+ """
self._finish.specular = dmnsn_new_phong(strength, size)
cdef class Reflection(Finish):
+ """Reflective finish."""
def __init__(self, min, max = None, double falloff = 1.0):
+ """
+ Create a Reflection.
+
+ Keyword arguments:
+ min -- color and intensity of reflection at indirect angles
+ max -- color and intensity of reflection at direct angles (default: min)
+ falloff -- exponent for reflection falloff (default: 1.0)
+ """
if max is None:
max = min
self._finish.reflection = dmnsn_new_basic_reflection(Color(min)._c,
@@ -542,12 +723,19 @@ cdef class Reflection(Finish):
############
cdef class Texture:
+ """Object surface properties."""
cdef dmnsn_texture *_texture
def __init__(self, pigment = None, Finish finish = None):
+ """
+ Create a Texture.
+
+ Keyword arguments:
+ pigment -- the Pigment for the texture, or a color (default: None)
+ finish -- the Finish for the texture (default: None)
+ """
self._texture = dmnsn_new_texture()
- cdef Pigment realPigment
if pigment is not None:
self.pigment = Pigment(pigment)
@@ -558,22 +746,24 @@ cdef class Texture:
dmnsn_delete_texture(self._texture)
property pigment:
+ """The texture's pigment."""
def __get__(self):
- return _rawPigment(self._texture.pigment)
+ return _Pigment(self._texture.pigment)
def __set__(self, Pigment pigment not None):
dmnsn_delete_pigment(self._texture.pigment)
self._texture.pigment = pigment._pigment
DMNSN_INCREF(self._texture.pigment)
property finish:
+ """The texture's finish."""
def __get__(self):
- return _rawFinish(self._texture.finish)
+ return _Finish(self._texture.finish)
def __set__(self, Finish finish not None):
dmnsn_delete_finish(self._texture.finish)
self._texture.finish = finish._finish
dmnsn_finish_incref(&self._texture.finish)
-cdef _rawTexture(dmnsn_texture *texture):
+cdef _Texture(dmnsn_texture *texture):
cdef Texture self = Texture.__new__(Texture)
self._texture = texture
DMNSN_INCREF(self._texture)
@@ -584,16 +774,30 @@ cdef _rawTexture(dmnsn_texture *texture):
#############
cdef class Interior:
+ """Object interior properties."""
cdef dmnsn_interior *_interior
def __init__(self, double ior = 1.0):
+ """
+ Create an Interior.
+
+ Keyword arguments:
+ ior -- index of reflection
+ """
self._interior = dmnsn_new_interior()
self._interior.ior = ior
def __dealloc__(self):
dmnsn_delete_interior(self._interior)
-cdef _rawInterior(dmnsn_interior *interior):
+ property ior:
+ """Index of reflection."""
+ def __get__(self):
+ return self._interior.ior
+ def __set__(self, double ior):
+ self._interior.ior = ior
+
+cdef _Interior(dmnsn_interior *interior):
cdef Interior self = Interior.__new__(Interior)
self._interior = interior
DMNSN_INCREF(self._interior)
@@ -604,12 +808,23 @@ cdef _rawInterior(dmnsn_interior *interior):
###########
cdef class Object:
+ """Physical objects."""
cdef dmnsn_object *_object
def __cinit__(self):
self._object = NULL
def __init__(self, Texture texture = None, Interior interior = None):
+ """
+ Initialize an Object.
+
+ Keyword arguments:
+ texture -- the object's Texture
+ interior -- the object's Interior
+ """
+ if self._object == NULL:
+ raise TypeError("attempt to initialize base Object")
+
if texture is not None:
self._object.texture = texture._texture
DMNSN_INCREF(self._object.texture)
@@ -621,37 +836,66 @@ cdef class Object:
dmnsn_delete_object(self._object)
def transform(self, Matrix trans not None):
- if self._object == NULL:
- raise TypeError('attempt to transform base Object')
-
+ """Transform an object."""
self._object.trans = dmnsn_matrix_mul(trans._m, self._object.trans)
return self
# Transform an object without affecting the texture
- cdef _intrinsicTransform(self, Matrix trans):
+ cdef _intrinsic_transform(self, Matrix trans):
self._object.trans = dmnsn_matrix_mul(self._object.trans, trans._m)
if self._object.texture != NULL:
self._object.texture.trans = dmnsn_matrix_mul(self._object.texture.trans,
trans.inverse()._m)
cdef class Plane(Object):
+ """A plane."""
def __init__(self, normal, double distance, *args, **kwargs):
+ """
+ Create a Plane.
+
+ Keyword arguments:
+ normal -- a vector perpendicular to the plane
+ distance -- the distance from the origin to the plane, in the direction of
+ normal
+
+ Additionally, Plane() accepts any arguments that Object() accepts.
+ """
self._object = dmnsn_new_plane(Vector(normal)._v)
Object.__init__(self, *args, **kwargs)
- self._intrinsicTransform(translate(distance*Vector(normal)))
+ self._intrinsic_transform(translate(distance*Vector(normal)))
cdef class Sphere(Object):
+ """A sphere."""
def __init__(self, center, double radius, *args, **kwargs):
+ """
+ Create a Sphere.
+
+ Keyword arguments:
+ center -- the center of the sphere
+ radius -- the radius of the sphere
+
+ Additionally, Sphere() accepts any arguments that Object() accepts.
+ """
self._object = dmnsn_new_sphere()
Object.__init__(self, *args, **kwargs)
cdef Matrix trans = translate(Vector(center))
trans *= scale(radius, radius, radius)
- self._intrinsicTransform(trans)
+ self._intrinsic_transform(trans)
cdef class Box(Object):
+ """An axis-aligned rectangular prism."""
def __init__(self, min, max, *args, **kwargs):
+ """
+ Create a Box.
+
+ Keyword arguments:
+ min -- the coordinate-wise minimal extent of the box
+ max -- the coordinate-wise maximal extent of the box
+
+ Additionally, Box() accepts any arguments that Object() accepts.
+ """
self._object = dmnsn_new_cube()
Object.__init__(self, *args, **kwargs)
@@ -659,12 +903,25 @@ cdef class Box(Object):
max = Vector(max)
cdef Matrix trans = translate((max + min)/2)
trans *= scale((max - min)/2)
- self._intrinsicTransform(trans)
+ self._intrinsic_transform(trans)
cdef class Cone(Object):
- def __init__(self, bottom, double bottomRadius, top, double topRadius,
+ """A cone or cone slice."""
+ def __init__(self, bottom, double bottom_radius, top, double top_radius = 0.0,
bool open not None = False, *args, **kwargs):
- self._object = dmnsn_new_cone(bottomRadius, topRadius, open)
+ """
+ Create a Cone.
+
+ Keyword arguments:
+ bottom -- the location of the bottom of the cone
+ bottom_radius -- the radius at the bottom of the cone
+ top -- the location of the top of the cone
+ top_radius -- the radius at the top of the cone/cone slice (default 0.0)
+ open -- whether to draw the cone cap(s)
+
+ Additionally, Cone() accepts any arguments that Object() accepts.
+ """
+ self._object = dmnsn_new_cone(bottom_radius, top_radius, open)
Object.__init__(self, *args, **kwargs)
# Lift the cone to start at the origin, then scale, rotate, and translate
@@ -674,27 +931,59 @@ cdef class Cone(Object):
cdef Matrix trans = translate(Y)
trans = scale(1.0, dir.norm()/2, 1.0)*trans
- trans = _rawMatrix(dmnsn_alignment_matrix(dmnsn_y, dir._v, dmnsn_x, dmnsn_z))*trans
+ trans = _Matrix(dmnsn_alignment_matrix(dmnsn_y, dir._v, dmnsn_x, dmnsn_z))*trans
trans = translate(bottom)*trans
- self._intrinsicTransform(trans)
+ self._intrinsic_transform(trans)
cdef class Cylinder(Cone):
+ """A cylinder."""
def __init__(self, bottom, top, double radius, bool open not None = False):
+ """
+ Create a Cylinder.
+
+ Keyword arguments:
+ bottom -- the location of the bottom of the cylinder
+ top -- the location of the top of the cylinder
+ radius -- the radius of the cylinder
+ open -- whether to draw the cylinder caps
+
+ Additionally, Cylinder() accepts any arguments that Object() accepts.
+ """
Cone.__init__(self,
- bottom = bottom, bottomRadius = radius,
- top = top, topRadius = radius,
+ bottom = bottom, bottom_radius = radius,
+ top = top, top_radius = radius,
open = open)
cdef class Torus(Object):
- def __init__(self, double majorRadius, double minorRadius, *args, **kwargs):
- self._object = dmnsn_new_torus(majorRadius, minorRadius)
+ """A torus."""
+ def __init__(self, double major_radius, double minor_radius, *args, **kwargs):
+ """
+ Create a Torus.
+
+ Keyword arguments:
+ major_radius -- the distance from the center of the torus to the center of
+ a circular cross-section of the torus
+ minor_radius -- the radius of the circular cross-sections of the torus
+
+ Additionally, Torus() accepts any arguments that Object() accepts.
+ """
+ self._object = dmnsn_new_torus(major_radius, minor_radius)
Object.__init__(self, *args, **kwargs)
cdef class Union(Object):
+ """A CSG union."""
def __init__(self, objects, *args, **kwargs):
+ """
+ Create a Union.
+
+ Keyword arguments:
+ objects -- a list of objects to include in the union
+
+ Additionally, Union() accepts any arguments that Object() accepts.
+ """
if len(objects) < 2:
- raise TypeError('expected a list of two or more Objects')
+ raise TypeError("expected a list of two or more Objects")
cdef dmnsn_array *array = dmnsn_new_array(sizeof(dmnsn_object *))
cdef dmnsn_object *o
@@ -712,9 +1001,18 @@ cdef class Union(Object):
Object.__init__(self, *args, **kwargs)
cdef class Intersection(Object):
+ """A CSG intersection."""
def __init__(self, objects, *args, **kwargs):
+ """
+ Create an Intersection.
+
+ Keyword arguments:
+ objects -- a list of objects to include in the intersection
+
+ Additionally, Intersection() accepts any arguments that Object() accepts.
+ """
if len(objects) < 2:
- raise TypeError('expected a list of two or more Objects')
+ raise TypeError("expected a list of two or more Objects")
cdef dmnsn_object *o
@@ -730,9 +1028,18 @@ cdef class Intersection(Object):
Object.__init__(self, *args, **kwargs)
cdef class Difference(Object):
+ """A CSG difference."""
def __init__(self, objects, *args, **kwargs):
+ """
+ Create a Difference.
+
+ Keyword arguments:
+ objects -- a list of objects to include in the difference
+
+ Additionally, Difference() accepts any arguments that Object() accepts.
+ """
if len(objects) < 2:
- raise TypeError('expected a list of two or more Objects')
+ raise TypeError("expected a list of two or more Objects")
cdef dmnsn_object *o
@@ -748,9 +1055,18 @@ cdef class Difference(Object):
Object.__init__(self, *args, **kwargs)
cdef class Merge(Object):
+ """A CSG merge."""
def __init__(self, objects, *args, **kwargs):
+ """
+ Create a Merge.
+
+ Keyword arguments:
+ objects -- a list of objects to include in the merge
+
+ Additionally, Merge() accepts any arguments that Object() accepts.
+ """
if len(objects) < 2:
- raise TypeError('expected a list of two or more Objects')
+ raise TypeError("expected a list of two or more Objects")
cdef dmnsn_object *o
@@ -770,13 +1086,22 @@ cdef class Merge(Object):
##########
cdef class Light:
+ """A light."""
cdef dmnsn_light *_light
def __dealloc__(self):
dmnsn_delete_light(self._light)
cdef class PointLight(Light):
+ """A point light."""
def __init__(self, location, color):
+ """
+ Create a PointLight.
+
+ Keyword arguments:
+ location -- the origin of the light rays
+ color -- the color and intensity of the light
+ """
# Take the sRGB component because "color = 0.5*White" should really mean
# a half-intensity white light
self._light = dmnsn_new_point_light(Vector(location)._v, Color(color)._sRGB)
@@ -787,6 +1112,7 @@ cdef class PointLight(Light):
###########
cdef class Camera:
+ """A camera."""
cdef dmnsn_camera *_camera
def __cinit__(self):
@@ -796,33 +1122,44 @@ cdef class Camera:
dmnsn_delete_camera(self._camera)
def transform(self, Matrix trans not None):
+ """Transform a camera."""
if self._camera == NULL:
- raise TypeError('attempt to transform base Camera')
+ raise TypeError("attempt to transform base Camera")
self._camera.trans = dmnsn_matrix_mul(trans._m, self._camera.trans)
return self
cdef class PerspectiveCamera(Camera):
- def __init__(self, location = -Z, lookAt = 0, sky = Y,
- angle = dmnsn_degrees(atan(0.5))):
+ """A regular perspective camera."""
+ def __init__(self, location = -Z, look_at = 0, sky = Y,
+ angle = dmnsn_degrees(atan(1.0))):
+ """
+ Create a PerspectiveCamera.
+
+ Keyword arguments:
+ location -- the location of the camera (default: -Z)
+ look_at -- where to aim the camera (default: 0)
+ sky -- the direction of the top of the camera (default: Y)
+ angle -- the field of view angle (from bottom to top) (default: 45)
+ """
self._camera = dmnsn_new_perspective_camera()
Camera.__init__(self)
# Apply the field of view angle
- self.transform(scale(2*tan(dmnsn_radians(angle))*(X + Y) + Z))
+ self.transform(scale(tan(dmnsn_radians(angle))*(X + Y) + Z))
- cdef Vector dir = Vector(lookAt) - Vector(location)
+ cdef Vector dir = Vector(look_at) - Vector(location)
cdef Vector vsky = Vector(sky)
# Line up the top of the viewport with the sky vector
- cdef Matrix alignSky = _rawMatrix(dmnsn_alignment_matrix(dmnsn_y, vsky._v,
- dmnsn_z, dmnsn_x))
- cdef Vector forward = alignSky*Z
- cdef Vector right = alignSky*X
+ cdef Matrix align_sky = _Matrix(dmnsn_alignment_matrix(dmnsn_y, vsky._v,
+ dmnsn_z, dmnsn_x))
+ cdef Vector forward = align_sky*Z
+ cdef Vector right = align_sky*X
- # Line up the look at point with lookAt
- self.transform(_rawMatrix(dmnsn_alignment_matrix(forward._v, dir._v,
- vsky._v, right._v)))
+ # Line up the look at point with look_at
+ self.transform(_Matrix(dmnsn_alignment_matrix(forward._v, dir._v,
+ vsky._v, right._v)))
# Move the camera into position
self.transform(translate(Vector(location)))
@@ -832,22 +1169,31 @@ cdef class PerspectiveCamera(Camera):
###############
cdef class SkySphere:
- cdef dmnsn_sky_sphere *_skySphere
+ """A scene background."""
+ cdef dmnsn_sky_sphere *_sky_sphere
def __init__(self, pigments):
- self._skySphere = dmnsn_new_sky_sphere()
+ """
+ Create a SkySphere.
+
+ Keyword arguments:
+ pigments -- the list of pigments that make up the background, in back-to-
+ front order
+ """
+ self._sky_sphere = dmnsn_new_sky_sphere()
- cdef Pigment realPigment
+ cdef Pigment real_pigment
for pigment in pigments:
- realPigment = Pigment(pigment)
- DMNSN_INCREF(realPigment._pigment)
- dmnsn_array_push(self._skySphere.pigments, &realPigment._pigment)
+ real_pigment = Pigment(pigment)
+ DMNSN_INCREF(real_pigment._pigment)
+ dmnsn_array_push(self._sky_sphere.pigments, &real_pigment._pigment)
def __dealloc__(self):
- dmnsn_delete_sky_sphere(self._skySphere)
+ dmnsn_delete_sky_sphere(self._sky_sphere)
def transform(self, Matrix trans not None):
- self._skySphere.trans = dmnsn_matrix_mul(trans._m, self._skySphere.trans)
+ """Transform a sky sphere."""
+ self._sky_sphere.trans = dmnsn_matrix_mul(trans._m, self._sky_sphere.trans)
return self
##########
@@ -855,10 +1201,20 @@ cdef class SkySphere:
##########
cdef class Scene:
+ """An entire scene."""
cdef dmnsn_scene *_scene
def __init__(self, Canvas canvas not None, objects, lights,
Camera camera not None):
+ """
+ Create a Scene.
+
+ Keyword arguments:
+ canvas -- the rendering Canvas
+ objects -- the list of objects in the scene
+ lights -- the list of lights in the scene
+ camera -- the camera for the scene
+ """
self._scene = dmnsn_new_scene()
self._scene.canvas = canvas._canvas
@@ -884,59 +1240,75 @@ cdef class Scene:
self._scene.camera = camera._camera
DMNSN_INCREF(self._scene.camera)
- property defaultTexture:
+ property default_texture:
+ """The default Texture for objects."""
def __get__(self):
- return _rawTexture(self._scene.default_texture)
+ return _Texture(self._scene.default_texture)
def __set__(self, Texture texture not None):
dmnsn_delete_texture(self._scene.default_texture)
self._scene.default_texture = texture._texture
DMNSN_INCREF(self._scene.default_texture)
- property defaultInterior:
+ property default_interior:
+ """The default Interior for objects."""
def __get__(self):
- return _rawInterior(self._scene.default_interior)
+ return _Interior(self._scene.default_interior)
def __set__(self, Interior interior not None):
dmnsn_delete_interior(self._scene.default_interior)
self._scene.default_interior = interior._interior
DMNSN_INCREF(self._scene.default_interior)
property background:
+ """The solid background color of the scene (default: Black)."""
def __get__(self):
- return _rawColor(self._scene.background)
+ return _Color(self._scene.background)
def __set__(self, color):
self._scene.background = Color(color)._c
- property skySphere:
- def __set__(self, SkySphere skySphere not None):
+ property sky_sphere:
+ """The background sky pattern of the scene."""
+ def __set__(self, SkySphere sky_sphere not None):
dmnsn_delete_sky_sphere(self._scene.sky_sphere)
- self._scene.sky_sphere = skySphere._skySphere
+ self._scene.sky_sphere = sky_sphere._sky_sphere
DMNSN_INCREF(self._scene.sky_sphere)
- property adcBailout:
+ property adc_bailout:
+ """The adaptive depth control bailout (default: 1/255)."""
def __get__(self):
return self._scene.adc_bailout
def __set__(self, double bailout):
self._scene.adc_bailout = bailout
- property recursionLimit:
+ property recursion_limit:
+ """The rendering recursion limit (default: 5)."""
def __get__(self):
return self._scene.reclimit
def __set__(self, level):
self._scene.reclimit = level
- property nThreads:
+ property nthreads:
+ """The number of threads to use for the render."""
def __get__(self):
return self._scene.nthreads
def __set__(self, n):
self._scene.nthreads = n
- property boundingTimer:
+ property bounding_timer:
+ """The Timer for building the bounding hierarchy."""
def __get__(self):
- return _rawTimer(self._scene.bounding_timer)
- property renderTimer:
+ if self._scene.bounding_timer == NULL:
+ raise RuntimeError('scene has not been rendered yet')
+
+ return _Timer(self._scene.bounding_timer)
+ property render_timer:
+ """The Timer for the actual render."""
def __get__(self):
- return _rawTimer(self._scene.render_timer)
+ if self._scene.render_timer == NULL:
+ raise RuntimeError('scene has not been rendered yet')
+
+ return _Timer(self._scene.render_timer)
def raytrace(self):
+ """Render the scene."""
# Ensure the default texture is complete
cdef Texture default = Texture(Black)
dmnsn_texture_cascade(default._texture, &self._scene.default_texture)