Panda3D
Loading...
Searching...
No Matches
pythonLoaderFileType.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file pythonLoaderFileType.cxx
10 * @author rdb
11 * @date 2019-07-29
12 */
13
15
16#ifdef HAVE_PYTHON
17
18#include "modelRoot.h"
19#include "pythonThread.h"
20#include "py_panda.h"
21#include "virtualFileSystem.h"
22
23extern struct Dtool_PyTypedObject Dtool_BamCacheRecord;
24extern struct Dtool_PyTypedObject Dtool_Filename;
25extern struct Dtool_PyTypedObject Dtool_LoaderOptions;
26extern struct Dtool_PyTypedObject Dtool_PandaNode;
27extern struct Dtool_PyTypedObject Dtool_PythonLoaderFileType;
28
29TypeHandle PythonLoaderFileType::_type_handle;
30
31/**
32 * This constructor expects init() to be called manually.
33 */
34PythonLoaderFileType::
35PythonLoaderFileType() {
36 init_type();
37}
38
39/**
40 * This constructor expects a single pkg_resources.EntryPoint argument for a
41 * deferred loader.
42 */
43PythonLoaderFileType::
44PythonLoaderFileType(std::string extension, PyObject *entry_point) :
45 _extension(std::move(extension)),
46 _entry_point(entry_point) {
47
48 init_type();
49 Py_INCREF(entry_point);
50}
51
52/**
53 * Destructor.
54 */
55PythonLoaderFileType::
56~PythonLoaderFileType() {
57 if (_entry_point != nullptr || _load_func != nullptr || _save_func != nullptr) {
58#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
59 PyGILState_STATE gstate;
60 gstate = PyGILState_Ensure();
61#endif
62
63 Py_CLEAR(_entry_point);
64 Py_CLEAR(_load_func);
65 Py_CLEAR(_save_func);
66
67#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
68 PyGILState_Release(gstate);
69#endif
70 }
71}
72
73/**
74 * Initializes the fields from the given Python loader object.
75 */
76bool PythonLoaderFileType::
77init(PyObject *loader) {
78 nassertr(loader != nullptr, false);
79 nassertr(_load_func == nullptr, false);
80 nassertr(_save_func == nullptr, false);
81
82 // Check the extensions member. If we already have a registered extension,
83 // it must occur in the list.
84 PyObject *extensions = PyObject_GetAttrString(loader, "extensions");
85 if (extensions != nullptr) {
86 if (PyUnicode_Check(extensions)
87#if PY_MAJOR_VERSION < 3
88 || PyString_Check(extensions)
89#endif
90 ) {
91 Dtool_Raise_TypeError("extensions list should be a list or tuple");
92 Py_DECREF(extensions);
93 return false;
94 }
95
96 PyObject *sequence = PySequence_Fast(extensions, "extensions must be a sequence");
97 PyObject **items = PySequence_Fast_ITEMS(sequence);
98 Py_ssize_t num_items = PySequence_Fast_GET_SIZE(sequence);
99 Py_DECREF(extensions);
100
101 if (num_items == 0) {
102 PyErr_SetString(PyExc_ValueError, "extensions list may not be empty");
103 Py_DECREF(sequence);
104 return false;
105 }
106
107 bool found_extension = false;
108
109 for (Py_ssize_t i = 0; i < num_items; ++i) {
110 PyObject *extension = items[i];
111 const char *extension_str;
112 Py_ssize_t extension_len;
113 #if PY_MAJOR_VERSION >= 3
114 extension_str = PyUnicode_AsUTF8AndSize(extension, &extension_len);
115 #else
116 if (PyString_AsStringAndSize(extension, (char **)&extension_str, &extension_len) == -1) {
117 extension_str = nullptr;
118 }
119 #endif
120
121 if (extension_str == nullptr) {
122 Py_DECREF(sequence);
123 return false;
124 }
125
126 if (_extension.empty()) {
127 _extension.assign(extension_str, extension_len);
128 found_extension = true;
129 } else {
130 std::string new_extension(extension_str, extension_len);
131 if (_extension == new_extension) {
132 found_extension = true;
133 } else {
134 if (!_additional_extensions.empty()) {
135 _additional_extensions += ' ';
136 }
137 _additional_extensions += new_extension;
138 }
139 }
140 }
141 Py_DECREF(sequence);
142
143 if (!found_extension) {
144 PyObject *repr = PyObject_Repr(loader);
145 loader_cat.error()
146 << "Registered extension '" << _extension
147 << "' does not occur in extensions list of "
148#if PY_MAJOR_VERSION >= 3
149 << PyUnicode_AsUTF8(repr) << "\n";
150#else
151 << PyString_AsString(repr) << "\n";
152#endif
153 Py_DECREF(repr);
154 return false;
155 }
156 } else {
157 return false;
158 }
159
160 PyObject *supports_compressed = PyObject_GetAttrString(loader, "supports_compressed");
161 if (supports_compressed != nullptr) {
162 if (supports_compressed == Py_True) {
163 _supports_compressed = true;
164 }
165 else if (supports_compressed == Py_False) {
166 _supports_compressed = false;
167 }
168 else {
169 Dtool_Raise_TypeError("supports_compressed must be a bool");
170 Py_DECREF(supports_compressed);
171 return false;
172 }
173 Py_DECREF(supports_compressed);
174 }
175 else {
176 PyErr_Clear();
177 }
178
179 _load_func = PyObject_GetAttrString(loader, "load_file");
180 PyErr_Clear();
181 _save_func = PyObject_GetAttrString(loader, "save_file");
182 PyErr_Clear();
183
184 if (_load_func == nullptr && _save_func == nullptr) {
185#if PY_MAJOR_VERSION >= 3
186 PyErr_Format(PyExc_TypeError,
187 "loader plug-in %R does not define load_file or save_file function",
188 loader);
189#else
190 PyObject *repr = PyObject_Repr(loader);
191 PyErr_Format(PyExc_TypeError,
192 "loader plug-in %s does not define load_file or save_file function",
193 PyString_AsString(repr));
194 Py_DECREF(repr);
195#endif
196 return false;
197 }
198
199 // We don't need this any more.
200 Py_CLEAR(_entry_point);
201
202 return true;
203}
204
205/**
206 * Ensures that the referenced Python module is loaded.
207 */
208bool PythonLoaderFileType::
209ensure_loaded() const {
210 if (_load_func != nullptr || _save_func != nullptr) {
211 return true;
212 }
213 nassertr_always(_entry_point != nullptr, false);
214
215#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
216 PyGILState_STATE gstate;
217 gstate = PyGILState_Ensure();
218#endif
219
220 if (loader_cat.is_info()) {
221 PyObject *repr = PyObject_Repr(_entry_point);
222
223 loader_cat.info()
224 << "loading file type module: "
225#if PY_MAJOR_VERSION >= 3
226 << PyUnicode_AsUTF8(repr) << "\n";
227#else
228 << PyString_AsString(repr) << "\n";
229#endif
230 Py_DECREF(repr);
231 }
232
233 PyObject *result = PyObject_CallMethod(_entry_point, (char *)"load", nullptr);
234
235 bool success = false;
236 if (result != nullptr) {
237 success = ((PythonLoaderFileType *)this)->init(result);
238 } else {
239 PyErr_Clear();
240 PyObject *repr = PyObject_Repr(_entry_point);
241
242 loader_cat.error()
243 << "unable to load "
244#if PY_MAJOR_VERSION >= 3
245 << PyUnicode_AsUTF8(repr) << "\n";
246#else
247 << PyString_AsString(repr) << "\n";
248#endif
249 Py_DECREF(repr);
250 }
251
252#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
253 PyGILState_Release(gstate);
254#endif
255
256 return success;
257}
258
259/**
260 *
261 */
262std::string PythonLoaderFileType::
263get_name() const {
264 return "Python loader";
265}
266
267/**
268 *
269 */
270std::string PythonLoaderFileType::
271get_extension() const {
272 return _extension;
273}
274
275/**
276 * Returns a space-separated list of extension, in addition to the one
277 * returned by get_extension(), that are recognized by this converter.
278 */
279std::string PythonLoaderFileType::
280get_additional_extensions() const {
281 return _additional_extensions;
282}
283
284/**
285 * Returns true if this file type can transparently load compressed files
286 * (with a .pz or .gz extension), false otherwise.
287 */
288bool PythonLoaderFileType::
289supports_compressed() const {
290 return ensure_loaded() && _supports_compressed;
291}
292
293/**
294 * Returns true if the file type can be used to load files, and load_file() is
295 * supported. Returns false if load_file() is unimplemented and will always
296 * fail.
297 */
298bool PythonLoaderFileType::
299supports_load() const {
300 return ensure_loaded() && _load_func != nullptr;
301}
302
303/**
304 * Returns true if the file type can be used to save files, and save_file() is
305 * supported. Returns false if save_file() is unimplemented and will always
306 * fail.
307 */
308bool PythonLoaderFileType::
309supports_save() const {
310 return ensure_loaded() && _save_func != nullptr;
311}
312
313/**
314 *
315 */
316PT(PandaNode) PythonLoaderFileType::
317load_file(const Filename &path, const LoaderOptions &options,
318 BamCacheRecord *record) const {
319 // Let's check whether the file even exists before calling Python.
321 PT(VirtualFile) vfile = vfs->get_file(path);
322 if (vfile == nullptr) {
323 return nullptr;
324 }
325
326 if (!supports_load()) {
327 return nullptr;
328 }
329
330 if (record != nullptr) {
331 record->add_dependent_file(vfile);
332 }
333
334#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
335 PyGILState_STATE gstate;
336 gstate = PyGILState_Ensure();
337#endif
338
339 // Wrap the arguments.
340 PyObject *args = PyTuple_New(3);
341 PyTuple_SET_ITEM(args, 0, DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true));
342 PyTuple_SET_ITEM(args, 1, DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true));
343 if (record != nullptr) {
344 record->ref();
345 PyTuple_SET_ITEM(args, 2, DTool_CreatePyInstanceTyped((void *)record, Dtool_BamCacheRecord, true, false, record->get_type_index()));
346 } else {
347 PyTuple_SET_ITEM(args, 2, Py_None);
348 Py_INCREF(Py_None);
349 }
350
351 PT(PandaNode) node;
352
353 PyObject *result = PythonThread::call_python_func(_load_func, args);
354 if (result != nullptr) {
355 if (DtoolInstance_Check(result)) {
356 node = (PandaNode *)DtoolInstance_UPCAST(result, Dtool_PandaNode);
357 }
358 Py_DECREF(result);
359 }
360
361 Py_DECREF(args);
362
363 if (node == nullptr) {
364 PyObject *exc_type = _PyErr_OCCURRED();
365 if (!exc_type) {
366 loader_cat.error()
367 << "load_file must return valid PandaNode or raise exception\n";
368 } else {
369 loader_cat.error()
370 << "Loading " << path.get_basename()
371 << " failed with " << ((PyTypeObject *)exc_type)->tp_name << " exception.\n";
372 PyErr_Clear();
373 }
374 }
375
376#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
377 PyGILState_Release(gstate);
378#endif
379
380 if (node != nullptr && node->is_of_type(ModelRoot::get_class_type())) {
381 ModelRoot *model_root = DCAST(ModelRoot, node.p());
382 model_root->set_fullpath(path);
383 model_root->set_timestamp(vfile->get_timestamp());
384 }
385
386 return node;
387}
388
389/**
390 *
391 */
392bool PythonLoaderFileType::
393save_file(const Filename &path, const LoaderOptions &options,
394 PandaNode *node) const {
395 if (!supports_save()) {
396 return false;
397 }
398
399 nassertr(node != nullptr, false);
400 node->ref();
401
402#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
403 PyGILState_STATE gstate;
404 gstate = PyGILState_Ensure();
405#endif
406
407 // Wrap the arguments.
408 PyObject *args = PyTuple_New(3);
409 PyTuple_SET_ITEM(args, 0, DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true));
410 PyTuple_SET_ITEM(args, 1, DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true));
411 PyTuple_SET_ITEM(args, 2, DTool_CreatePyInstanceTyped((void *)node, Dtool_PandaNode, true, false, node->get_type_index()));
412
413 PyObject *result = PythonThread::call_python_func(_load_func, args);
414 Py_DECREF(args);
415 if (result != nullptr) {
416 Py_DECREF(result);
417 } else {
418 PyErr_Clear();
419 loader_cat.error()
420 << "save_file failed with an exception.\n";
421 }
422
423#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
424 PyGILState_Release(gstate);
425#endif
426
427 return (result != nullptr);
428}
429
430#endif // HAVE_PYTHON
An instance of this class is written to the front of a Bam or Txo file to make the file a cached inst...
void add_dependent_file(const Filename &pathname)
Adds the indicated file to the list of files that will be loaded to generate the data in this record.
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
std::string get_basename() const
Returns the basename part of the filename.
Definition filename.I:367
Specifies parameters that may be passed to the loader.
A node of this type is created automatically at the root of each model file that is loaded.
Definition modelRoot.h:27
set_fullpath
Sets the full pathname of the model represented by this node, as found on disk.
Definition modelRoot.h:37
set_timestamp
Sets the timestamp of the file on disk that was read for this model.
Definition modelRoot.h:41
A basic node of the scene graph or data graph.
Definition pandaNode.h:65
void ref() const
Explicitly increments the reference count.
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
int get_type_index() const
Returns the internal index number associated with this object's TypeHandle, a unique number for each ...
Definition typedObject.I:20
A hierarchy of directories and files that appears to be one continuous file system,...
PointerTo< VirtualFile > get_file(const Filename &filename, bool status_only=false) const
Looks up the file by the indicated name in the file system.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
The abstract base class for a file or directory within the VirtualFileSystem.
Definition virtualFile.h:35
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
STL namespace.
PyObject * DTool_CreatePyInstance(const T *obj, bool memory_rules)
These functions wrap a pointer for a class that defines get_type_handle().
Definition py_panda.I:124
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.