Panda3D
loader.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 loader.cxx
10  * @author mike
11  * @date 1997-01-09
12  */
13 
14 #include "loader.h"
15 #include "loaderFileType.h"
16 #include "loaderFileTypeRegistry.h"
17 #include "config_pgraph.h"
18 #include "modelPool.h"
19 #include "modelLoadRequest.h"
20 #include "modelSaveRequest.h"
21 #include "config_express.h"
22 #include "config_putil.h"
23 #include "virtualFileSystem.h"
24 #include "filename.h"
25 #include "load_dso.h"
26 #include "string_utils.h"
27 #include "bamCache.h"
28 #include "bamCacheRecord.h"
29 #include "sceneGraphReducer.h"
30 #include "renderState.h"
31 #include "bamFile.h"
32 #include "configVariableInt.h"
33 #include "configVariableEnum.h"
34 
35 using std::string;
36 
37 bool Loader::_file_types_loaded = false;
38 PT(Loader) Loader::_global_ptr;
39 TypeHandle Loader::_type_handle;
40 
41 /**
42  *
43  */
44 Loader::
45 Loader(const string &name) :
46  Namable(name)
47 {
48  _task_manager = AsyncTaskManager::get_global_ptr();
49  _task_chain = name;
50 
51  if (_task_manager->find_task_chain(_task_chain) == nullptr) {
52  PT(AsyncTaskChain) chain = _task_manager->make_task_chain(_task_chain);
53 
54  ConfigVariableInt loader_num_threads
55  ("loader-num-threads", 1,
56  PRC_DESC("The number of threads that will be started by the Loader class "
57  "to load models asynchronously. These threads will only be "
58  "started if the asynchronous interface is used, and if threading "
59  "support is compiled into Panda. The default is one thread, "
60  "which allows models to be loaded one at a time in a single "
61  "asychronous thread. You can set this higher, particularly if "
62  "you have many CPU's available, to allow loading multiple models "
63  "simultaneously."));
64  chain->set_num_threads(loader_num_threads);
65 
66  ConfigVariableEnum<ThreadPriority> loader_thread_priority
67  ("loader-thread-priority", TP_low,
68  PRC_DESC("The default thread priority to assign to the threads created "
69  "for asynchronous loading. The default is 'low'; you may "
70  "also specify 'normal', 'high', or 'urgent'."));
71  chain->set_thread_priority(loader_thread_priority);
72  }
73 }
74 
75 /**
76  * Returns a new AsyncTask object suitable for adding to load_async() to start
77  * an asynchronous model load.
78  */
79 PT(AsyncTask) Loader::
80 make_async_request(const Filename &filename, const LoaderOptions &options) {
81  return new ModelLoadRequest(string("model:")+filename.get_basename(),
82  filename, options, this);
83 }
84 
85 /**
86  * Returns a new AsyncTask object suitable for adding to save_async() to start
87  * an asynchronous model save.
88  */
89 PT(AsyncTask) Loader::
90 make_async_save_request(const Filename &filename, const LoaderOptions &options,
91  PandaNode *node) {
92  return new ModelSaveRequest(string("model_save:")+filename.get_basename(),
93  filename, options, node, this);
94 }
95 
96 /**
97  * Attempts to read a bam file from the indicated stream and return the scene
98  * graph defined there.
99  */
100 PT(PandaNode) Loader::
101 load_bam_stream(std::istream &in) {
102  BamFile bam_file;
103  if (!bam_file.open_read(in)) {
104  return nullptr;
105  }
106 
107  return bam_file.read_node();
108 }
109 
110 /**
111  *
112  */
113 void Loader::
114 output(std::ostream &out) const {
115  out << get_type() << " " << get_name();
116 
117  int num_tasks = _task_manager->make_task_chain(_task_chain)->get_num_tasks();
118  if (num_tasks != 0) {
119  out << " (" << num_tasks << " models pending)";
120  }
121 }
122 
123 /**
124  * Loads a single scene graph file, if possible. Returns the Node that is the
125  * root of the file, or NULL if the file cannot be loaded.
126  *
127  * If search is true, the file is searched for along the model path;
128  * otherwise, only the exact filename is loaded.
129  */
130 PT(PandaNode) Loader::
131 load_file(const Filename &filename, const LoaderOptions &options) const {
132  Filename this_filename(filename);
133  LoaderOptions this_options(options);
134 
135  bool report_errors = (this_options.get_flags() & LoaderOptions::LF_report_errors) != 0;
136 
137  string extension = this_filename.get_extension();
138  if (extension.empty()) {
139  // If the filename has no filename extension, append the default extension
140  // specified in the Config file.
141  this_filename = this_filename.get_fullpath() + default_model_extension.get_value();
142  extension = this_filename.get_extension();
143  }
144 
145  bool compressed = false;
146 #ifdef HAVE_ZLIB
147  if (extension == "pz" || extension == "gz") {
148  compressed = true;
149  extension = Filename(this_filename.get_basename_wo_extension()).get_extension();
150  }
151 #endif // HAVE_ZLIB
152 
153  if (extension.empty()) {
154  if (report_errors) {
155  loader_cat.error()
156  << "Cannot load " << this_filename
157  << " without filename extension. Loading of model filenames with an "
158  "implicit extension is deprecated in Panda3D. Please "
159  "correct the filename reference. If necessary, you may put the "
160  "line \"default-model-extension .bam\" or \"default-model-extension .egg\" "
161  "in your Config.prc to globally assume a particular model "
162  "filename extension.\n";
163  }
164  return nullptr;
165  }
166 
168  LoaderFileType *requested_type = reg->get_type_from_extension(extension);
169  // requested_type may be nullptr at this point, because there's still a
170  // chance we can load it via the model-cache or by adding .bam to the end.
171  if (requested_type != nullptr) {
172  if (!requested_type->supports_load()) {
173  if (report_errors) {
174  loader_cat.error()
175  << requested_type->get_name() << " file type (."
176  << extension << ") does not support loading.\n";
177  }
178  return nullptr;
179  } else if (compressed && !requested_type->supports_compressed()) {
180  if (report_errors) {
181  loader_cat.error()
182  << requested_type->get_name() << " file type (."
183  << extension << ") does not support in-line compression.\n";
184  }
185  return nullptr;
186  }
187  }
188 
189  bool search = (this_options.get_flags() & LoaderOptions::LF_search) != 0;
190  if (!filename.is_local()) {
191  // If we have a global filename, we don't search the model path.
192  search = false;
193  }
194 
195  // Now that we've decided whether to search for the file, don't try to
196  // search again.
197  this_options.set_flags(this_options.get_flags() & ~LoaderOptions::LF_search);
198 
200 
201  if (search) {
202  // Look for the file along the model path.
203  DSearchPath model_path(get_model_path());
204  int num_dirs = model_path.get_num_directories();
205  for (int i = 0; i < num_dirs; ++i) {
206  Filename pathname(model_path.get_directory(i), this_filename);
207  PT(PandaNode) result = try_load_file(pathname, this_options,
208  requested_type);
209  if (result != nullptr) {
210  return result;
211  }
212  }
213 
214  if (report_errors) {
215  bool any_exist = false;
216  for (int i = 0; i < num_dirs; ++i) {
217  Filename pathname(model_path.get_directory(i), this_filename);
218  if (vfs->exists(pathname)) {
219  any_exist = true;
220  break;
221  }
222  }
223 
224  if (any_exist) {
225  if (requested_type == nullptr) {
226  loader_cat.error()
227  << "Extension of file " << this_filename
228  << " is unrecognized; cannot load.\n";
229  loader_cat.error(false)
230  << "Currently known scene file types are:\n";
231  reg->write(loader_cat.error(false), 2);
232  }
233  loader_cat.error()
234  << "Couldn't load file " << this_filename
235  << ": all matching files on model path invalid "
236  << "(the model path is currently: \"" << get_model_path() << "\")\n";
237  } else {
238  loader_cat.error()
239  << "Couldn't load file " << this_filename
240  << ": not found on model path "
241  << "(currently: \"" << get_model_path() << "\")\n";
242  }
243  }
244 
245  } else {
246  // Look for the file only where it is.
248  PT(PandaNode) result = try_load_file(this_filename, this_options, requested_type);
249  if (result != nullptr) {
250  return result;
251  }
252  if (report_errors) {
253  if (vfs->exists(this_filename)) {
254  if (requested_type == nullptr) {
255  loader_cat.error()
256  << "Extension of file " << this_filename
257  << " is unrecognized; cannot load.\n";
258  loader_cat.error(false)
259  << "Currently known scene file types are:\n";
260  reg->write(loader_cat.error(false), 2);
261  }
262  loader_cat.error()
263  << "Couldn't load file " << this_filename << ": invalid.\n";
264  } else {
265  loader_cat.error()
266  << "Couldn't load file " << this_filename << ": does not exist.\n";
267  }
268  }
269  }
270  return nullptr;
271 }
272 
273 /**
274  * The implementatin of load_file(), this tries a single possible file without
275  * searching further along the path.
276  */
277 PT(PandaNode) Loader::
278 try_load_file(const Filename &pathname, const LoaderOptions &options,
279  LoaderFileType *requested_type) const {
281 
282  bool allow_ram_cache =
283  ((options.get_flags() & LoaderOptions::LF_no_ram_cache) == 0);
284 
285  if (allow_ram_cache) {
286  // If we're allowing a RAM cache, use the ModelPool to load the file.
287  PT(PandaNode) node = ModelPool::get_model(pathname, true);
288  if (node != nullptr) {
289  if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) {
290  if (loader_cat.is_debug()) {
291  loader_cat.debug()
292  << "Model " << pathname << " found in ModelPool.\n";
293  }
294  // But return a deep copy of the shared model.
295  node = node->copy_subgraph();
296  }
297  return node;
298  }
299  }
300 
301  bool report_errors = ((options.get_flags() & LoaderOptions::LF_report_errors) != 0 || loader_cat.is_debug());
302 
303  PT(BamCacheRecord) record;
304  if (cache->get_cache_models() &&
305  (options.get_flags() & LoaderOptions::LF_no_disk_cache) == 0) {
306  // See if the model can be found in the on-disk cache, if it is active.
307  record = cache->lookup(pathname, "bam");
308  if (record != nullptr) {
309  if (record->has_data()) {
310  if (report_errors) {
311  loader_cat.info()
312  << "Model " << pathname << " found in disk cache.\n";
313  }
314  PT(PandaNode) result = DCAST(PandaNode, record->get_data());
315 
316  if (premunge_data) {
317  SceneGraphReducer sgr;
318  sgr.premunge(result, RenderState::make_empty());
319  }
320 
321  if (result->is_of_type(ModelRoot::get_class_type())) {
322  ModelRoot *model_root = DCAST(ModelRoot, result.p());
323  model_root->set_fullpath(pathname);
324  model_root->set_timestamp(record->get_source_timestamp());
325 
326  if (allow_ram_cache) {
327  // Store the loaded model in the RAM cache, and make sure we
328  // return a copy so that this node can be modified independently
329  // from the RAM cached version.
330  ModelPool::add_model(pathname, model_root);
331  if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) {
332  return model_root->copy_subgraph();
333  }
334  }
335  }
336  return result;
337  }
338  }
339 
340  if (loader_cat.is_debug()) {
341  loader_cat.debug()
342  << "Model " << pathname << " not found in cache.\n";
343  }
344  }
345 
346  if (options.get_flags() & LoaderOptions::LF_cache_only) {
347  // We're not allowed to read from disk.
348  return nullptr;
349  }
350 
351  // Load the model from disk.
352  PT(PandaNode) result;
353  if (requested_type != nullptr) {
354  result = requested_type->load_file(pathname, options, record);
355  }
356  if (result != nullptr) {
357  if (record != nullptr) {
358  // Store the loaded model in the model cache.
359  record->set_data(result);
360  cache->store(record);
361  }
362  } else {
363  // Do we have the same filename, but with .bam appended to the end?
364  string extension = pathname.get_extension();
365  Filename pathname_bam = pathname;
366  if (extension == "pz" || extension == "gz") {
367  // Strip .pz/.gz, so that model.egg.pz -> model.egg.bam
368  extension = pathname_bam.get_extension();
369  pathname_bam = pathname_bam.get_fullpath_wo_extension();
370  }
371  if (extension == "bam") {
372  // Don't try to load .bam.bam files, that is just silly.
373  return nullptr;
374  }
375  pathname_bam += ".bam";
376 
377  BamFile bam_file;
378  if (!bam_file.open_read(pathname_bam, report_errors)) {
379  return nullptr;
380  }
381 
382  bam_file.get_reader()->set_loader_options(options);
383  result = bam_file.read_node(report_errors);
384 
385  nassertr_always(result != nullptr && result->is_of_type(ModelRoot::get_class_type()), nullptr);
386  // We don't bother with the model-cache here, since this .bam file is
387  // already effectively a cached version of the original model.
388  }
389 
390  if (premunge_data) {
391  SceneGraphReducer sgr;
392  sgr.premunge(result, RenderState::make_empty());
393  }
394 
395  if (allow_ram_cache && result->is_of_type(ModelRoot::get_class_type())) {
396  // Store the loaded model in the RAM cache, and make sure we return a
397  // copy so that this node can be modified independently from the RAM
398  // cached version.
399  ModelPool::add_model(pathname, DCAST(ModelRoot, result.p()));
400  if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) {
401  result = result->copy_subgraph();
402  }
403  }
404 
405  return result;
406 }
407 
408 /**
409  * Saves a scene graph to a single file, if possible. The file type written
410  * is implicit in the filename extension.
411  */
412 bool Loader::
413 save_file(const Filename &filename, const LoaderOptions &options,
414  PandaNode *node) const {
415  Filename this_filename(filename);
416  LoaderOptions this_options(options);
417 
418  bool report_errors = (this_options.get_flags() & LoaderOptions::LF_report_errors) != 0;
419 
420  string extension = this_filename.get_extension();
421  if (extension.empty()) {
422  // If the filename has no filename extension, append the default extension
423  // specified in the Config file.
424  this_filename = this_filename.get_fullpath() + default_model_extension.get_value();
425  extension = this_filename.get_extension();
426  }
427 
428  bool compressed = false;
429 #ifdef HAVE_ZLIB
430  if (extension == "pz" || extension == "gz") {
431  compressed = true;
432  extension = Filename(this_filename.get_basename_wo_extension()).get_extension();
433  }
434 #endif // HAVE_ZLIB
435 
436  if (extension.empty()) {
437  if (report_errors) {
438  loader_cat.error()
439  << "Cannot save " << this_filename
440  << " without filename extension.\n";
441  }
442  return false;
443  }
444 
446  LoaderFileType *requested_type =
447  reg->get_type_from_extension(extension);
448 
449  if (requested_type == nullptr) {
450  if (report_errors) {
451  loader_cat.error()
452  << "Extension of file " << this_filename
453  << " is unrecognized; cannot save.\n";
454  loader_cat.error(false)
455  << "Currently known scene file types are:\n";
456  reg->write(loader_cat.error(false), 2);
457  }
458  return false;
459 
460  } else if (!requested_type->supports_save()) {
461  if (report_errors) {
462  loader_cat.error()
463  << requested_type->get_name() << " file type (."
464  << extension << ") does not support saving.\n";
465  }
466  return false;
467 
468  } else if (compressed && !requested_type->supports_compressed()) {
469  if (report_errors) {
470  loader_cat.error()
471  << requested_type->get_name() << " file type (."
472  << extension << ") does not support in-line compression.\n";
473  }
474  return false;
475  }
476 
477  bool result = try_save_file(this_filename, this_options, node, requested_type);
478  if (!result) {
479  if (report_errors) {
480  loader_cat.error()
481  << "Couldn't save file " << this_filename << ".\n";
482  }
483  }
484 
485  return result;
486 }
487 
488 /**
489  * The implementation of save_file(), this tries to write a specific file
490  * type.
491  */
492 bool Loader::
493 try_save_file(const Filename &pathname, const LoaderOptions &options,
494  PandaNode *node, LoaderFileType *requested_type) const {
495  return requested_type->save_file(pathname, options, node);
496 }
497 
498 /**
499  * Loads up all of the dynamic libraries named in a load-file-type Configure
500  * variable. Presumably this will make the various file types available for
501  * runtime loading.
502  */
503 void Loader::
504 load_file_types() {
505  if (!_file_types_loaded) {
506  int num_unique_values = load_file_type.get_num_unique_values();
507 
508  for (int i = 0; i < num_unique_values; i++) {
509  string param = load_file_type.get_unique_value(i);
510 
511  vector_string words;
512  extract_words(param, words);
513 
514  if (words.size() == 1) {
515  // Exactly one word: load the named library immediately.
516  string name = words[0];
517  Filename dlname = Filename::dso_filename("lib" + name + ".so");
518  loader_cat.info()
519  << "loading file type module: " << name << std::endl;
520  void *tmp = load_dso(get_plugin_path().get_value(), dlname);
521  if (tmp == nullptr) {
522  loader_cat.warning()
523  << "Unable to load " << dlname.to_os_specific()
524  << ": " << load_dso_error() << std::endl;
525  } else if (loader_cat.is_debug()) {
526  loader_cat.debug()
527  << "done loading file type module: " << name << std::endl;
528  }
529 
530  } else if (words.size() > 1) {
531  // Multiple words: the first n words are filename extensions, and the
532  // last word is the name of the library to load should any of those
533  // filename extensions be encountered.
535  size_t num_extensions = words.size() - 1;
536  string library_name = words[num_extensions];
537 
538  for (size_t i = 0; i < num_extensions; i++) {
539  string extension = words[i];
540  if (extension[0] == '.') {
541  extension = extension.substr(1);
542  }
543 
544  registry->register_deferred_type(extension, library_name);
545  }
546  }
547  }
548 
549  _file_types_loaded = true;
550  }
551 }
552 
553 /**
554  * Called once per application to create the global loader object.
555  */
556 void Loader::
557 make_global_ptr() {
558  nassertv(_global_ptr == nullptr);
559 
560  _global_ptr = new Loader("loader");
561 }
The principle public interface to reading and writing Bam disk files.
Definition: bamFile.h:41
A node of this type is created automatically at the root of each model file that is loaded.
Definition: modelRoot.h:27
void register_deferred_type(const std::string &extension, const std::string &library)
Records a type associated with a particular extension to be loaded in the future.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A basic node of the scene graph or data graph.
Definition: pandaNode.h:64
get_cache_models
Returns whether model files (e.g.
Definition: bamCache.h:89
static void add_model(const Filename &filename, ModelRoot *model)
Adds the indicated already-loaded model to the pool.
Definition: modelPool.I:70
set_data
Stores a new data object on the record.
bool open_read(const Filename &bam_filename, bool report_errors=true)
Attempts to open the indicated filename for reading.
Definition: bamFile.cxx:51
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A class object that manages a single asynchronous model save request.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
size_t get_num_unique_values() const
Returns the number of unique values in the variable.
Specifies parameters that may be passed to the loader.
Definition: loaderOptions.h:23
This class maintains a cache of Bam and/or Txo objects generated from model files and texture images ...
Definition: bamCache.h:42
A hierarchy of directories and files that appears to be one continuous file system,...
static LoaderFileTypeRegistry * get_global_ptr()
Returns a pointer to the global LoaderFileTypeRegistry object.
void write(std::ostream &out, int indent_level=0) const
Writes a list of supported file types to the indicated output stream, one per line.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An interface for simplifying ("flattening") scene graphs by eliminating unneeded nodes and collapsing...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A convenient class for loading models from disk, in bam or egg format (or any of a number of other fo...
Definition: loader.h:42
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PT(AsyncTask) Loader
Returns a new AsyncTask object suitable for adding to load_async() to start an asynchronous model loa...
Definition: loader.cxx:79
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void output(std::ostream &out) const
Outputs the Namable.
Definition: namable.I:61
bool store(BamCacheRecord *record)
Flushes a cache entry to disk.
Definition: bamCache.cxx:188
A base class for all things which can have a name.
Definition: namable.h:26
bool exists(const Filename &filename) const
Convenience function; returns true if the named file exists.
int extract_words(const string &str, vector_string &words)
Divides the string into a number of words according to whitespace.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An instance of this class is written to the front of a Bam or Txo file to make the file a cached inst...
virtual bool supports_load() const
Returns true if the file type can be used to load files, and load_file() is supported.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_data
Returns a pointer to the data stored in the record, or NULL if there is no data.
virtual bool supports_compressed() const
Returns true if this file type can transparently load compressed files (with a .pz or ....
void premunge(PandaNode *root, const RenderState *initial_state)
Walks the scene graph rooted at this node and below, and uses the indicated GSG to premunge every Geo...
The AsyncTaskChain is a subset of the AsyncTaskManager.
This class specializes ConfigVariable as an enumerated type.
This class maintains the set of all known LoaderFileTypes in the universe.
std::string get_unique_value(size_t n) const
Returns the nth unique value of the variable.
get_value
Returns the variable's value.
bool is_local() const
Returns true if the filename is local, e.g.
Definition: filename.I:549
std::string get_extension() const
Returns the file extension.
Definition: filename.I:400
std::string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:367
virtual bool supports_save() const
Returns true if the file type can be used to save files, and save_file() is supported.
get_reader
Returns the BamReader in charge of performing the read operations.
Definition: bamFile.h:81
This class represents a concrete task performed by an AsyncManager.
Definition: asyncTask.h:32
LoaderFileType * get_type_from_extension(const std::string &extension)
Determines the type of the file based on the indicated extension (without a leading dot).
set_timestamp
Sets the timestamp of the file on disk that was read for this model.
Definition: modelRoot.h:41
This is the base class for a family of scene-graph file types that the Loader supports.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is a convenience class to specialize ConfigVariable as an integer type.
This class stores a list of directories that can be searched, in order, to locate a particular file.
Definition: dSearchPath.h:28
A class object that manages a single asynchronous model load request.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
Definition: filename.cxx:1123
static AsyncTaskManager * get_global_ptr()
Returns a pointer to the global AsyncTaskManager.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::string get_fullpath_wo_extension() const
Returns the full filename–directory and basename parts–except for the extension.
Definition: filename.I:377
static BamCache * get_global_ptr()
Returns a pointer to the global BamCache object, which is used automatically by the ModelPool and Tex...
Definition: bamCache.I:223
static ModelRoot * get_model(const Filename &filename, bool verify)
Returns the model that has already been previously loaded, or NULL otherwise.
Definition: modelPool.I:46
set_fullpath
Sets the full pathname of the model represented by this node, as found on disk.
Definition: modelRoot.h:37