nsi.py

  1"""
  2Python binding to 3Delight's Nodal Scene Interface
  3"""
  4
  5BAD_CONTEXT = 0
  6
  7SCENE_ROOT = '.root'
  8SCENE_GLOBAL = '.global'
  9ALL_NODES = '.all'
 10
 11import ctypes
 12import os
 13import platform
 14
 15# Load 3Delight
 16if platform.system() == "Windows":
 17    _lib3delight = ctypes.cdll.LoadLibrary('3Delight')
 18elif platform.system() == "Darwin":
 19    __delight = os.getenv('DELIGHT')
 20    if __delight is None:
 21        __delight = '/Applications/3Delight'
 22    _lib3delight = ctypes.cdll.LoadLibrary(__delight + '/lib/lib3delight.dylib')
 23else:
 24    _lib3delight = ctypes.cdll.LoadLibrary('lib3delight.so')
 25
 26
 27class _NSIParam_t(ctypes.Structure):
 28    """
 29    Python version of the NSIParam_t struct to interface with the C API.
 30    """
 31    _fields_ = [
 32        ("name", ctypes.c_char_p),
 33        ("data", ctypes.c_void_p),
 34        ("type", ctypes.c_int),
 35        ("arraylength", ctypes.c_int),
 36        ("count", ctypes.c_size_t),
 37        ("flags", ctypes.c_int)
 38        ]
 39
 40class Type:
 41    """
 42    Python version of the C NSIType_t enum
 43    """
 44    Invalid = 0
 45    Float = 1
 46    Double = Float | 0x10
 47    Integer = 2
 48    String = 3
 49    Color = 4
 50    Point = 5
 51    Vector = 6
 52    Normal = 7
 53    Matrix = 8
 54    DoubleMatrix = Matrix | 0x10
 55    Pointer = 9
 56
 57_nsi_type_num_elements = {
 58    Type.Float : 1,
 59    Type.Double : 1,
 60    Type.Integer : 1,
 61    Type.String : 1,
 62    Type.Color : 3,
 63    Type.Point : 3,
 64    Type.Vector : 3,
 65    Type.Normal : 3,
 66    Type.Matrix : 16,
 67    Type.DoubleMatrix : 16,
 68    Type.Pointer : 1 }
 69
 70class Flags:
 71    """
 72    Python version of the NSIParam_t flags values
 73    """
 74    IsArray = 1
 75    PerFace = 2
 76    PerVertex = 4
 77    InterpolateLinear = 8
 78
 79def _GetArgNSIType(value):
 80    if isinstance(value, Arg):
 81        if value.type is not None:
 82            return value.type
 83        else:
 84            return _GetArgNSIType(value.value)
 85    if isinstance(value, (tuple, list)):
 86        return _GetArgNSIType(value[0])
 87    if isinstance(value, (int, bool)):
 88        return Type.Integer
 89    if isinstance(value, float):
 90        return Type.Double
 91    if isinstance(value, str):
 92        return Type.String
 93    return Type.Invalid
 94
 95def _GetArgCType(value):
 96    nsitype = _GetArgNSIType(value)
 97    typemap = {
 98        Type.Float : ctypes.c_float,
 99        Type.Double : ctypes.c_double,
100        Type.Integer : ctypes.c_int,
101        Type.String : ctypes.c_char_p,
102        Type.Color : ctypes.c_float,
103        Type.Point : ctypes.c_float,
104        Type.Vector : ctypes.c_float,
105        Type.Normal : ctypes.c_float,
106        Type.Matrix : ctypes.c_float,
107        Type.DoubleMatrix : ctypes.c_double,
108        Type.Pointer : ctypes.c_void_p
109    }
110    return typemap.get(nsitype)
111
112def _BuildOneCArg(nsiparam, value):
113    """
114    Fill one _NSIParam_t object from an argument value.
115    """
116    # TODO: Support numpy.matrix as DoubleMatrix argument.
117    datatype = _GetArgCType(value)
118    arraylength = None
119    countoverride = None
120    flags = 0
121    v = value
122    if isinstance(value, Arg):
123        v = value.value
124        arraylength = value.arraylength
125        countoverride = value.count
126        flags = value.flags
127
128    if v is None:
129        nsiparam.type = Type.Invalid
130        return
131
132    if isinstance(v, ctypes.c_void_p):
133        # Raw data given with ctypes. Must use nsi.Arg. No safety here.
134        datacount = 0 # Will be overriden by countoverride.
135        nsiparam.data = v
136    elif isinstance(v, (tuple, list)):
137        # Data is multiple values (eg. a list of floats).
138        datacount = len(v)
139        arraytype = datatype * datacount;
140        if v and isinstance(v[0], str):
141            # Encode all the strings to utf-8
142            fixedv = [x.encode('utf-8') for x in v]
143            nsiparam.data = ctypes.cast(ctypes.pointer(
144                arraytype(*fixedv)), ctypes.c_void_p)
145        else:
146            nsiparam.data = ctypes.cast(ctypes.pointer(
147                arraytype(*v)), ctypes.c_void_p)
148    else:
149        # Data is a single object (string, float, int).
150        datacount = 1
151        if isinstance(v, str):
152            nsiparam.data = ctypes.cast(ctypes.pointer(
153                datatype(v.encode('utf-8'))), ctypes.c_void_p)
154        else:
155            nsiparam.data = ctypes.cast(ctypes.pointer(
156                datatype(v)), ctypes.c_void_p)
157
158    valuecount = datacount
159
160    nsitype = _GetArgNSIType(value)
161    numelements = _nsi_type_num_elements.get(nsitype, 0)
162    if numelements == 0:
163        valuecount = 0
164    else:
165        valuecount = int(valuecount / numelements)
166
167    if arraylength is not None:
168        nsiparam.arraylength = arraylength
169        flags |= Flags.IsArray
170        if arraylength == 0:
171            valuecount = 0
172        else:
173            valuecount = int(valuecount / arraylength)
174
175    if countoverride is not None:
176        if countoverride < valuecount or isinstance(v, ctypes.c_void_p):
177            valuecount = countoverride
178
179    nsiparam.type = nsitype
180    nsiparam.count = valuecount
181    nsiparam.flags = flags
182
183def _BuildCArgumentList(args):
184    cargs_type = _NSIParam_t * len(args)
185    cargs = cargs_type()
186    for i, arg in enumerate(args.items()):
187        cargs[i].name = arg[0].encode('utf-8')
188        _BuildOneCArg(cargs[i], arg[1])
189    return cargs
190
191class Context:
192    """
193    A NSI context.
194
195    All NSI operations are done in a specific context. Multiple contexts may
196    cohexist.
197
198    Most methods of the Context accept a named argument list. The argument
199    values can be native python integer, string or float (which is given to NSI
200    as a double). More complex types should use one of the Arg classes in this
201    module.
202    """
203
204    def __init__(self, handle=None):
205        """
206        If an integer handle argument is provided, this object will be bound to
207        an existing NSI context with that handle.
208
209        It is not required that the context have been created by the python
210        binding.
211        """
212        self._handle = BAD_CONTEXT
213
214    def Begin(self, **arglist):
215        """
216        Create a new NSI context and bind this object to it.
217        """
218        a = _BuildCArgumentList(arglist)
219        self._handle = _lib3delight.NSIBegin(len(a), a)
220
221    def End(self):
222        """
223        Release the context.
224
225        If this object was bound to an external handle, that handle will on
226        longer be valid after this call.
227        """
228        # TODO: Support with statement
229        if self._handle != BAD_CONTEXT:
230            _lib3delight.NSIEnd( self._handle )
231
232    def Create(self, handle, type, **arglist):
233        """
234        Create a new node.
235
236        Parameters
237        handle : The handle of the node to create.
238        type : The type of node to create.
239        """
240        a = _BuildCArgumentList(arglist)
241        _lib3delight.NSICreate(
242            self._handle,
243            handle.encode('utf-8'),
244            type.encode('utf-8'),
245            len(a), a)
246
247    def Delete(self, handle, **arglist):
248        """
249        Delete a node.
250
251        Parameters
252        handle : The handle of the node to delete.
253        """
254        a = _BuildCArgumentList(arglist)
255        _lib3delight.NSIDelete(
256            self._handle,
257            handle.encode('utf-8'),
258            len(a), a)
259
260    def SetAttribute(self, handle, **arglist):
261        """
262        Set attributes of a node.
263
264        Parameters
265        handle : The handle of the node on which to set attributes.
266        """
267        a = _BuildCArgumentList(arglist)
268        _lib3delight.NSISetAttribute(
269            self._handle,
270            handle.encode('utf-8'),
271            len(a), a)
272
273    def SetAttributeAtTime(self, handle, time, **arglist):
274        """
275        Set attributes of a node for a specific time.
276
277        Parameters
278        handle : The handle of the node on which to set attributes.
279        time : The time for which the attributes are set.
280        """
281        a = _BuildCArgumentList(arglist)
282        _lib3delight.NSISetAttributeAtTime(
283            self._handle,
284            handle.encode('utf-8'),
285            ctypes.c_double(time),
286            len(a), a)
287
288    def DeleteAttribute(self, handle, name):
289        """
290        Delete an attribute of a node.
291
292        Parameters
293        handle : The handle of the node on which to delete an attribute.
294        name : The name of the attribute to delete.
295        """
296        _lib3delight.NSIDeleteAttribute(
297            self._handle,
298            handle.encode('utf-8'),
299            name.encode('utf-8'))
300
301    def Connect(self, from_handle, from_attr, to_handle, to_attr, **arglist):
302        """
303        Connect nodes or specific attributes of nodes.
304
305        Parameters
306        from_handle : The handle of the node to connect from.
307        from_attr : Optional attribute to connect from.
308        to_handle : The handle of the node to connect to.
309        to_attr : Optional attribute to connect to.
310        """
311        a = _BuildCArgumentList(arglist)
312        _lib3delight.NSIConnect(
313            self._handle,
314            from_handle.encode('utf-8'),
315            (from_attr if from_attr else '').encode('utf-8'),
316            to_handle.encode('utf-8'),
317            (to_attr if to_attr else '').encode('utf-8'),
318            len(a), a)
319
320    def Disconnect(self, from_handle, from_attr, to_handle, to_attr, **arglist):
321        """
322        Disconnect nodes or specific attributes of nodes.
323
324        Parameters
325        from_handle : The handle of the node to disconnect from.
326        from_attr : Optional attribute to disconnect from.
327        to_handle : The handle of the node to disconnect to.
328        to_attr : Optional attribute to disconnect to.
329        """
330        a = _BuildCArgumentList(arglist)
331        _lib3delight.NSIDisconnect(
332            self._handle,
333            from_handle.encode('utf-8'),
334            (from_attr if from_attr else '').encode('utf-8'),
335            to_handle.encode('utf-8'),
336            (to_attr if to_attr else '').encode('utf-8'))
337
338    def Evaluate(self, **arglist):
339        """
340        Evaluate NSI commands from some other source.
341
342        This can read other files, run scripts, etc.
343        """
344        a = _BuildCArgumentList(arglist)
345        _lib3delight.NSIEvaluate(
346            self._handle,
347            len(a), a)
348
349    def RenderControl(self, **arglist):
350        """
351        Control rendering.
352
353        This is used to start and stop renders, wait for them to complete, etc.
354        """
355        a = _BuildCArgumentList(arglist)
356        _lib3delight.NSIRenderControl(
357            self._handle,
358            len(a), a)
359
360class Arg:
361    """
362    Wrapper for NSI parameter list values.
363
364    NSI functions which accept a parameter list may be given values wrapped in
365    an Arg object to specify details about the argument. For example, 3 float
366    values are normally output as 3 NSITypeDouble values. To set a color
367    instead, give nsi.Arg((0.4, 0.2, 0.5), type=nsi.Type.Color)
368
369    The most common types have specific wrappers which are easier to use. The
370    above example could instead be nsi.ColorArg(0.4, 0.2, 0.5)
371    """
372    def __init__(self, v, type=None, arraylength=None, flags=None, count=None):
373        """
374        Parameters
375        v : The value.
376        type : An optional value from nsi.Type
377        arraylength : An optional integer to specify the array length of the
378        base type. For example, 2 for texture coordinates.
379        flags : Optional flags from nsi.Flags
380        count : Number of values of the base type.
381        """
382        self.type = None
383        self.arraylength = None
384        self.flags = 0
385        self.count = None
386
387        if isinstance(v, Arg):
388            # Fold its attributes into this object. This allows chaining Arg
389            # objects.
390            self.value = v.value
391            self.type = v.type
392            self.flags = v.flags
393            self.count = v.count
394        else:
395            self.value = v
396
397        if arraylength is not None:
398            self.arraylength = arraylength
399        if type is not None:
400            self.type = type
401        if flags is not None:
402            self.flags |= flags
403        if count is not None:
404            self.count = count
405
406class IntegerArg(Arg):
407    """
408    Wrapper for NSI parameter list integer value.
409
410    Use as nsi.IntegerArg(2). This is generally not needed as it is the default
411    behavior when an int is given. Using this class will enforce the type.
412    """
413    def __init__(self, v):
414        Arg.__init__(self, int(v), type=Type.Integer)
415
416class FloatArg(Arg):
417    """
418    Wrapper for NSI parameter list float value.
419
420    Use as nsi.FloatArg(0.5).
421    """
422    def __init__(self, v):
423        Arg.__init__(self, v, type=Type.Float)
424
425class DoubleArg(Arg):
426    """
427    Wrapper for NSI parameter list double value.
428
429    Use as nsi.DoubleArg(0.5).
430    """
431    def __init__(self, v):
432        Arg.__init__(self, v, type=Type.Double)
433
434class ColorArg(Arg):
435    """
436    Wrapper for NSI parameter list color value.
437
438    Use as nsi.ColorArg(0.2, 0.3, 0.4) or nsi.ColorArg(0.5)
439    """
440    def __init__(self, r, g=None, b=None):
441        if b is None:
442            Arg.__init__(self, (r,r,r), type=Type.Color)
443        else:
444            Arg.__init__(self, (r,g,b), type=Type.Color)
445
446# vim: set softtabstop=4 expandtab shiftwidth=4: