2011-08-17 16:44:28 +08:00
|
|
|
\section{Python integration}
|
|
|
|
\label{sec:python}
|
|
|
|
A recent feature of Mitsuba is a simple Python interface to the renderer API.
|
|
|
|
While the interface is still limited at this point, it can already be
|
2011-08-22 06:54:13 +08:00
|
|
|
used for many useful purposes. To access the API, start your Python
|
2011-08-19 15:13:18 +08:00
|
|
|
interpreter and enter
|
2011-08-17 16:44:28 +08:00
|
|
|
\begin{python}
|
|
|
|
import mitsuba
|
|
|
|
\end{python}
|
|
|
|
For this to work on MacOS X, you will first have to run the ``\emph{Apple
|
2011-08-19 15:13:18 +08:00
|
|
|
Menu}$\to$\emph{Command-line access}'' menu item from within Mitsuba.
|
|
|
|
On Windows and non-packaged Linux builds, you may have to update the extension
|
2011-08-22 06:54:13 +08:00
|
|
|
search path before issuing the \code{import} command, e.g.:
|
2011-08-17 16:44:28 +08:00
|
|
|
\begin{python}
|
|
|
|
import sys
|
|
|
|
|
|
|
|
# Update the extension search path
|
|
|
|
# (may vary depending on your setup)
|
|
|
|
sys.path.append('dist/python')
|
|
|
|
|
|
|
|
import mitsuba
|
|
|
|
\end{python}
|
2011-08-22 06:54:13 +08:00
|
|
|
For an overview of the currently exposed API subset, please refer
|
2011-08-19 15:13:18 +08:00
|
|
|
to the following page: \url{http://www.mitsuba-renderer.org/api/group__libpython.html}.
|
|
|
|
|
2011-08-22 06:54:13 +08:00
|
|
|
\subsection{Basics}
|
|
|
|
Generally, the Python API tries to mimic the C++ API as closely as possible.
|
|
|
|
Where applicable, the Python classes and methods replicate overloaded operators,
|
|
|
|
overridable virtual function calls, and default arguments. Under rare circumstances,
|
|
|
|
some features are inherently non-portable due to fundamental differences between the
|
|
|
|
two programming languages. In this case, the API documentation will contain further
|
|
|
|
information.
|
2011-08-19 15:13:18 +08:00
|
|
|
|
2011-08-22 06:54:13 +08:00
|
|
|
Mitsuba's linear algebra-related classes are usable with essentially the
|
|
|
|
same syntax as their C++ versions --- for example, the following snippet
|
|
|
|
creates and rotates a unit vector.
|
2011-08-19 15:13:18 +08:00
|
|
|
\begin{python}
|
|
|
|
import mitsuba
|
|
|
|
from mitsuba.core import *
|
|
|
|
|
2011-08-22 06:54:13 +08:00
|
|
|
# Create a normalized direction vector
|
2011-08-19 15:13:18 +08:00
|
|
|
myVector = normalize(Vector(1.0, 2.0, 3.0))
|
|
|
|
|
2011-08-22 06:54:13 +08:00
|
|
|
# 90 deg. rotation around the Y axis
|
|
|
|
trafo = Transform.rotate(Vector(0, 1, 0), 90)
|
2011-08-19 15:13:18 +08:00
|
|
|
|
2011-08-22 06:54:13 +08:00
|
|
|
# Apply the rotation and display the result
|
|
|
|
print(trafo * myVector)
|
2011-08-19 15:13:18 +08:00
|
|
|
\end{python}
|
|
|
|
|
2011-08-22 06:54:13 +08:00
|
|
|
\subsection{Recipes}
|
|
|
|
The following section contains a series of ``recipes'' on how to do
|
|
|
|
certain things with the help of the Python bindings.
|
|
|
|
|
|
|
|
\subsubsection{Loading a scene}
|
|
|
|
The following script demonstrates how to use the
|
|
|
|
\code{FileResolver} and \code{SceneHandler} classes to
|
|
|
|
load a Mitsuba scene from an XML file:
|
|
|
|
\begin{python}
|
|
|
|
import mitsuba
|
|
|
|
|
|
|
|
from mitsuba.core import *
|
|
|
|
from mitsuba.render import SceneHandler
|
|
|
|
|
|
|
|
# Get a reference to the thread's file resolver
|
|
|
|
fileResolver = Thread.getThread().getFileResolver()
|
|
|
|
|
|
|
|
# Add the search path needed to load plugins
|
|
|
|
fileResolver.addPath('<path to mitsuba directory>')
|
|
|
|
|
|
|
|
# Add the search path needed to load scene resources
|
|
|
|
fileResolver.addPath('<path to scene directory>')
|
|
|
|
|
|
|
|
# Optional: supply parameters that can be accessed
|
|
|
|
# by the scene (e.g. as $\text{\color{lstcomment}\itshape\texttt{\$}}$myParameter)
|
|
|
|
paramMap = StringMap()
|
|
|
|
paramMap['myParameter'] = 'value'
|
|
|
|
|
|
|
|
# Load the scene from an XML file
|
|
|
|
scene = SceneHandler.loadScene(fileResolver, paramMap)
|
|
|
|
|
|
|
|
# Display a textual summary of the scene's contents
|
|
|
|
print(scene)
|
|
|
|
\end{python}
|
|
|
|
|
|
|
|
\subsubsection{Rendering a loaded scene}
|
|
|
|
Once a scene has been loaded, it can be rendered as follows:
|
|
|
|
\begin{python}
|
|
|
|
from mitsuba.core import *
|
|
|
|
from mitsuba.render import RenderQueue, RenderJob
|
|
|
|
import multiprocessing
|
|
|
|
|
|
|
|
scheduler = Scheduler.getInstance()
|
|
|
|
|
|
|
|
# Start up the scheduling system with one worker per local core
|
|
|
|
for i in range(0, multiprocessing.cpu_count()):
|
|
|
|
scheduler.registerWorker(LocalWorker('wrk%i' % i))
|
|
|
|
scheduler.start()
|
|
|
|
|
|
|
|
# Create a queue for tracking render jobs
|
|
|
|
queue = RenderQueue()
|
|
|
|
|
|
|
|
scene.setDestinationFile('renderedResult')
|
|
|
|
|
|
|
|
# Create a render job and insert it into the queue
|
|
|
|
job = RenderJob('myRenderJob', scene, queue)
|
|
|
|
job.start()
|
|
|
|
|
|
|
|
# Wait for all jobs to finish and release resources
|
|
|
|
queue.waitLeft(0)
|
|
|
|
queue.join()
|
|
|
|
|
|
|
|
# Print some statistics about the rendering process
|
|
|
|
print(Statistics.getInstance().getStats())
|
|
|
|
\end{python}
|
|
|
|
|
|
|
|
\subsubsection{Rendering over the network}
|
|
|
|
To render over the network, you must first set up one or
|
|
|
|
more machines that run the \code{mtssrv} server (see \secref{mtssrv}).
|
|
|
|
A network node can then be registered with the scheduler as follows:
|
|
|
|
\begin{python}
|
|
|
|
# Connect to a socket on a named host or IP address
|
|
|
|
# 7554 is the default port of 'mtssrv'
|
|
|
|
stream = SocketStream('128.84.103.222', 7554)
|
|
|
|
|
|
|
|
# Create a remote worker instance that communicates over the stream
|
|
|
|
remoteWorker = RemoteWorker('netWorker', stream)
|
|
|
|
|
|
|
|
scheduler = Scheduler.getInstance()
|
|
|
|
# Register the remote worker (and any other potential workers)
|
|
|
|
scheduler.registerWorker(remoteWorker)
|
|
|
|
scheduler.start()
|
|
|
|
\end{python}
|
|
|
|
|
|
|
|
\subsubsection{Constructing custom scenes from Python}
|
|
|
|
Dynamically constructing Mitsuba scenes entails loading a series of external
|
|
|
|
plugins, instantiating them with custom parameters, and finally assembling
|
|
|
|
them into an object graph.
|
|
|
|
For instance, the following snippet shows how to create a basic
|
|
|
|
perspective camera with a film that writes PNG images:
|
|
|
|
\begin{python}
|
|
|
|
from mitsuba.core import *
|
|
|
|
pmgr = PluginManager.getInstance()
|
|
|
|
|
|
|
|
# Encodes parameters on how to instantiate the 'perspective' plugin
|
|
|
|
cameraProps = Properties('perspective')
|
|
|
|
cameraProps['toWorld'] = Transform.lookAt(
|
|
|
|
Point(0, 0, -10), # Camera origin
|
|
|
|
Point(0, 0, 0), # Camera target
|
|
|
|
Vector(0, 1, 0) # 'up' vector
|
|
|
|
)
|
|
|
|
cameraProps['fov'] = 45.0
|
|
|
|
|
|
|
|
# Encodes parameters on how to instantiate the 'pngfilm' plugin
|
|
|
|
filmProps = Properties('pngfilm')
|
|
|
|
filmProps['width'] = 1920
|
|
|
|
filmProps['height'] = 1080
|
|
|
|
|
|
|
|
# Load and instantiate the plugins
|
|
|
|
camera = pmgr.createObject(cameraProps)
|
|
|
|
film = pmgr.createObject(filmProps)
|
|
|
|
|
|
|
|
# First configure the film and then add it to the camera
|
|
|
|
film.configure()
|
|
|
|
camera.addChild('film', film)
|
|
|
|
|
|
|
|
# Now, the camera can be configured
|
|
|
|
camera.configure()
|
|
|
|
\end{python}
|
|
|
|
The above code fragment uses the plugin manager to construct a
|
|
|
|
\code{Camera} instance from an external plugin named
|
|
|
|
\texttt{perspective.so/dll/dylib} and adds a child object
|
|
|
|
named \texttt{film}, which is a \texttt{Film} instance loaded from the
|
|
|
|
plugin \texttt{pngfilm.so/dll/dylib}.
|
|
|
|
Each time after instantiating a plugin, all child objects are added, and
|
|
|
|
finally the plugin's \code{configure()} method must be called.
|
|
|
|
|
|
|
|
Creating scenes in this manner ends up being rather laborious.
|
|
|
|
Since Python comes with a powerful dynamically-typed dictionary
|
|
|
|
primitive, Mitsuba additionally provides a more ``pythonic''
|
|
|
|
alternative that makes use of this facility:
|
|
|
|
\begin{python}
|
|
|
|
from mitsuba.core import *
|
|
|
|
|
|
|
|
pmgr = PluginManager.getInstance()
|
|
|
|
camera = pmgr.create({
|
|
|
|
'type' : 'perspective',
|
|
|
|
'toWorld' : Transform.lookAt(
|
|
|
|
Point(0, 0, -10),
|
|
|
|
Point(0, 0, 0),
|
|
|
|
Vector(0, 1, 0)
|
|
|
|
),
|
|
|
|
'film' : {
|
|
|
|
'type' : 'pngfilm',
|
|
|
|
'width' : 1920,
|
|
|
|
'height' : 1080
|
|
|
|
}
|
|
|
|
})
|
|
|
|
\end{python}
|
|
|
|
This code does exactly the same as the previous snippet.
|
|
|
|
By the time \code{PluginManager.create} returns, the object
|
|
|
|
hierarchy has already been assembled, and the
|
|
|
|
\code{configure()} method of every object
|
|
|
|
has been called.
|
|
|
|
|
|
|
|
Finally, here is an full example that creates a basic scene
|
|
|
|
which can be rendered. It describes a sphere lit by a point
|
|
|
|
light, rendered using the direct illumination integrator.
|
|
|
|
\begin{python}
|
|
|
|
from mitsuba.core import *
|
|
|
|
from mitsuba.render import Scene
|
|
|
|
|
|
|
|
scene = Scene()
|
|
|
|
|
|
|
|
# Create a camera, film & sample generator
|
|
|
|
scene.addChild(pmgr.create({
|
|
|
|
'type' : 'perspective',
|
|
|
|
'toWorld' : Transform.lookAt(
|
|
|
|
Point(0, 0, -10),
|
|
|
|
Point(0, 0, 0),
|
|
|
|
Vector(0, 1, 0)
|
|
|
|
),
|
|
|
|
'film' : {
|
|
|
|
'type' : 'pngfilm',
|
|
|
|
'width' : 1920,
|
|
|
|
'height' : 1080
|
|
|
|
},
|
|
|
|
'sampler' : {
|
|
|
|
'type' : 'ldsampler',
|
|
|
|
'sampleCount' : 2
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
|
|
|
|
# Set the integrator
|
|
|
|
scene.addChild(pmgr.create({
|
|
|
|
'type' : 'direct'
|
|
|
|
}))
|
|
|
|
|
|
|
|
# Add a light source
|
|
|
|
scene.addChild(pmgr.create({
|
|
|
|
'type' : 'point',
|
|
|
|
'position' : Point(5, 0, -10),
|
|
|
|
'intensity' : Spectrum(100)
|
|
|
|
}))
|
|
|
|
|
|
|
|
# Add a shape
|
|
|
|
scene.addChild(pmgr.create({
|
|
|
|
'type' : 'sphere',
|
|
|
|
'center' : Point(0, 0, 0),
|
|
|
|
'radius' : 1.0,
|
|
|
|
'bsdf' : {
|
|
|
|
'type' : 'diffuse',
|
|
|
|
'reflectance' : Spectrum(0.4)
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
\end{python}
|
|
|
|
|
|
|
|
\subsubsection{Taking control of the logging system}
|
2011-08-20 15:36:40 +08:00
|
|
|
Many operations in Mitsuba will print one or more log messages
|
|
|
|
during their execution. By default, they will be printed to the console,
|
|
|
|
which may be undesirable. Similar to the C++ side, it is possible to define
|
|
|
|
custom \code{Formatter} and \code{Appender} classes to interpret and direct
|
|
|
|
the flow of these messages.
|
2011-08-17 16:44:28 +08:00
|
|
|
|
2011-08-20 15:36:40 +08:00
|
|
|
Roughly, a \code{Formatter} turns detailed
|
|
|
|
information about a logging event into a human-readable string, and a
|
|
|
|
\code{Appender} routes it to some destination (e.g. by appending it to
|
|
|
|
a file or a log viewer in a graphical user interface). Here is an example
|
|
|
|
of how to activate such extensions:
|
|
|
|
\begin{python}
|
|
|
|
import mitsuba
|
|
|
|
from mitsuba.core import *
|
|
|
|
|
|
|
|
class MyFormatter(Formatter):
|
|
|
|
def format(self, logLevel, sourceClass, sourceThread, message, filename, line):
|
2011-08-22 06:54:13 +08:00
|
|
|
return '%s (log level: %s, thread: %s, class %s, file %s, line %i)' % \
|
2011-08-20 15:36:40 +08:00
|
|
|
(message, str(logLevel), sourceThread.getName(), sourceClass,
|
|
|
|
filename, line)
|
|
|
|
|
|
|
|
class MyAppender(Appender):
|
|
|
|
def append(self, logLevel, message):
|
|
|
|
print(message)
|
|
|
|
|
|
|
|
def logProgress(self, progress, name, formatted, eta):
|
2011-08-22 06:54:13 +08:00
|
|
|
print('Progress message: ' + formatted)
|
2011-08-20 15:36:40 +08:00
|
|
|
|
|
|
|
# Get the logger associated with the current thread
|
|
|
|
logger = Thread.getThread().getLogger()
|
|
|
|
logger.setFormatter(MyFormatter())
|
|
|
|
logger.clearAppenders()
|
|
|
|
logger.addAppender(MyAppender())
|
|
|
|
logger.setLogLevel(EDebug)
|
|
|
|
|
2011-08-22 06:54:13 +08:00
|
|
|
Log(EInfo, 'Test message')
|
2011-08-20 15:36:40 +08:00
|
|
|
\end{python}
|