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: