1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 
4 /// @file io/File.cc
5 
6 #include "File.h"
7 
8 #include "TempFile.h"
9 #include <openvdb/Exceptions.h>
10 #include <openvdb/util/logging.h>
11 #include <cstdint>
12 #include <boost/iostreams/copy.hpp>
13 #ifndef _MSC_VER
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17 #endif
18 #include <cassert>
19 #include <cstdlib> // for getenv(), strtoul()
20 #include <cstring> // for strerror_r()
21 #include <fstream>
22 #include <iostream>
23 #include <limits>
24 #include <sstream>
25 
26 
27 namespace openvdb {
28 OPENVDB_USE_VERSION_NAMESPACE
29 namespace OPENVDB_VERSION_NAME {
30 namespace io {
31 
32 // Implementation details of the File class
33 struct File::Impl
34 {
35     enum { DEFAULT_COPY_MAX_BYTES = 500000000 }; // 500 MB
36 
37     struct NoBBox {};
38 
39     // Common implementation of the various File::readGrid() overloads,
40     // with and without bounding box clipping
41     template<typename BoxType>
readGridopenvdb::OPENVDB_VERSION_NAME::io::File::Impl42     static GridBase::Ptr readGrid(const File& file, const GridDescriptor& gd, const BoxType& bbox)
43     {
44         // This method should not be called for files that don't contain grid offsets.
45         assert(file.inputHasGridOffsets());
46 
47         GridBase::Ptr grid = file.createGrid(gd);
48         gd.seekToGrid(file.inputStream());
49         unarchive(file, grid, gd, bbox);
50         return grid;
51     }
52 
unarchiveopenvdb::OPENVDB_VERSION_NAME::io::File::Impl53     static void unarchive(const File& file, GridBase::Ptr& grid,
54         const GridDescriptor& gd, NoBBox)
55     {
56         file.Archive::readGrid(grid, gd, file.inputStream());
57     }
58 
unarchiveopenvdb::OPENVDB_VERSION_NAME::io::File::Impl59     static void unarchive(const File& file, GridBase::Ptr& grid,
60         const GridDescriptor& gd, const CoordBBox& indexBBox)
61     {
62         file.Archive::readGrid(grid, gd, file.inputStream(), indexBBox);
63     }
64 
unarchiveopenvdb::OPENVDB_VERSION_NAME::io::File::Impl65     static void unarchive(const File& file, GridBase::Ptr& grid,
66         const GridDescriptor& gd, const BBoxd& worldBBox)
67     {
68         file.Archive::readGrid(grid, gd, file.inputStream(), worldBBox);
69     }
70 
getDefaultCopyMaxBytesopenvdb::OPENVDB_VERSION_NAME::io::File::Impl71     static Index64 getDefaultCopyMaxBytes()
72     {
73         Index64 result = DEFAULT_COPY_MAX_BYTES;
74         if (const char* s = std::getenv("OPENVDB_DELAYED_LOAD_COPY_MAX_BYTES")) {
75             char* endptr = nullptr;
76             result = std::strtoul(s, &endptr, /*base=*/10);
77         }
78         return result;
79     }
80 
81     std::string mFilename;
82     // The file-level metadata
83     MetaMap::Ptr mMeta;
84     // The memory-mapped file
85     MappedFile::Ptr mFileMapping;
86     // The buffer for the input stream, if it is a memory-mapped file
87     SharedPtr<std::streambuf> mStreamBuf;
88     // The file stream that is open for reading
89     std::unique_ptr<std::istream> mInStream;
90     // File-level stream metadata (file format, compression, etc.)
91     StreamMetadata::Ptr mStreamMetadata;
92     // Flag indicating if we have read in the global information (header,
93     // metadata, and grid descriptors) for this VDB file
94     bool mIsOpen;
95     // File size limit for copying during delayed loading
96     Index64 mCopyMaxBytes;
97     // Grid descriptors for all grids stored in the file, indexed by grid name
98     NameMap mGridDescriptors;
99     // All grids, indexed by unique name (used only when mHasGridOffsets is false)
100     Archive::NamedGridMap mNamedGrids;
101     // All grids stored in the file (used only when mHasGridOffsets is false)
102     GridPtrVecPtr mGrids;
103 }; // class File::Impl
104 
105 
106 ////////////////////////////////////////
107 
108 
File(const std::string & filename)109 File::File(const std::string& filename): mImpl(new Impl)
110 {
111     mImpl->mFilename = filename;
112     mImpl->mIsOpen = false;
113     mImpl->mCopyMaxBytes = Impl::getDefaultCopyMaxBytes();
114     setInputHasGridOffsets(true);
115 }
116 
117 
~File()118 File::~File()
119 {
120 }
121 
122 
File(const File & other)123 File::File(const File& other)
124     : Archive(other)
125     , mImpl(new Impl)
126 {
127     *this = other;
128 }
129 
130 
131 File&
operator =(const File & other)132 File::operator=(const File& other)
133 {
134     if (&other != this) {
135         Archive::operator=(other);
136         const Impl& otherImpl = *other.mImpl;
137         mImpl->mFilename = otherImpl.mFilename;
138         mImpl->mMeta = otherImpl.mMeta;
139         mImpl->mIsOpen = false; // don't want two file objects reading from the same stream
140         mImpl->mCopyMaxBytes = otherImpl.mCopyMaxBytes;
141         mImpl->mGridDescriptors = otherImpl.mGridDescriptors;
142         mImpl->mNamedGrids = otherImpl.mNamedGrids;
143         mImpl->mGrids = otherImpl.mGrids;
144     }
145     return *this;
146 }
147 
148 
149 SharedPtr<Archive>
copy() const150 File::copy() const
151 {
152     return SharedPtr<Archive>{new File{*this}};
153 }
154 
155 
156 ////////////////////////////////////////
157 
158 
159 const std::string&
filename() const160 File::filename() const
161 {
162     return mImpl->mFilename;
163 }
164 
165 
166 MetaMap::Ptr
fileMetadata()167 File::fileMetadata()
168 {
169     return mImpl->mMeta;
170 }
171 
172 MetaMap::ConstPtr
fileMetadata() const173 File::fileMetadata() const
174 {
175     return mImpl->mMeta;
176 }
177 
178 
179 const File::NameMap&
gridDescriptors() const180 File::gridDescriptors() const
181 {
182     return mImpl->mGridDescriptors;
183 }
184 
185 File::NameMap&
gridDescriptors()186 File::gridDescriptors()
187 {
188     return mImpl->mGridDescriptors;
189 }
190 
191 
192 std::istream&
inputStream() const193 File::inputStream() const
194 {
195     if (!mImpl->mInStream) {
196         OPENVDB_THROW(IoError, filename() << " is not open for reading");
197     }
198     return *mImpl->mInStream;
199 }
200 
201 
202 ////////////////////////////////////////
203 
204 
205 Index64
getSize() const206 File::getSize() const
207 {
208     /// @internal boost::filesystem::file_size() would be a more portable alternative,
209     /// but as of 9/2014, Houdini ships without the Boost.Filesystem library,
210     /// which makes it much less convenient to use that library.
211 
212     Index64 result = std::numeric_limits<Index64>::max();
213 
214     std::string mesg = "could not get size of file " + filename();
215 
216 #ifdef _MSC_VER
217     // Get the file size by seeking to the end of the file.
218     std::ifstream fstrm(filename());
219     if (fstrm) {
220         fstrm.seekg(0, fstrm.end);
221         result = static_cast<Index64>(fstrm.tellg());
222     } else {
223         OPENVDB_THROW(IoError, mesg);
224     }
225 #else
226     // Get the file size using the stat() system call.
227     struct stat info;
228     if (0 != ::stat(filename().c_str(), &info)) {
229         std::string s = getErrorString();
230         if (!s.empty()) mesg += " (" + s + ")";
231         OPENVDB_THROW(IoError, mesg);
232     }
233     if (!S_ISREG(info.st_mode)) {
234         mesg += " (not a regular file)";
235         OPENVDB_THROW(IoError, mesg);
236     }
237     result = static_cast<Index64>(info.st_size);
238 #endif
239 
240     return result;
241 }
242 
243 
244 Index64
copyMaxBytes() const245 File::copyMaxBytes() const
246 {
247     return mImpl->mCopyMaxBytes;
248 }
249 
250 
251 void
setCopyMaxBytes(Index64 bytes)252 File::setCopyMaxBytes(Index64 bytes)
253 {
254     mImpl->mCopyMaxBytes = bytes;
255 }
256 
257 
258 ////////////////////////////////////////
259 
260 
261 bool
isOpen() const262 File::isOpen() const
263 {
264     return mImpl->mIsOpen;
265 }
266 
267 
268 bool
open(bool delayLoad,const MappedFile::Notifier & notifier)269 File::open(bool delayLoad, const MappedFile::Notifier& notifier)
270 {
271     if (isOpen()) {
272         OPENVDB_THROW(IoError, filename() << " is already open");
273     }
274     mImpl->mInStream.reset();
275 
276     // Open the file.
277     std::unique_ptr<std::istream> newStream;
278     SharedPtr<std::streambuf> newStreamBuf;
279     MappedFile::Ptr newFileMapping;
280     if (!delayLoad || !Archive::isDelayedLoadingEnabled()) {
281         newStream.reset(new std::ifstream(
282             filename().c_str(), std::ios_base::in | std::ios_base::binary));
283     } else {
284         bool isTempFile = false;
285         std::string fname = filename();
286         if (getSize() < copyMaxBytes()) {
287             // If the file is not too large, make a temporary private copy of it
288             // and open the copy instead.  The original file can then be modified
289             // or removed without affecting delayed load.
290             try {
291                 TempFile tempFile;
292                 std::ifstream fstrm(filename().c_str(),
293                     std::ios_base::in | std::ios_base::binary);
294                 boost::iostreams::copy(fstrm, tempFile);
295                 fname = tempFile.filename();
296                 isTempFile = true;
297             } catch (std::exception& e) {
298                 std::string mesg;
299                 if (e.what()) mesg = std::string(" (") + e.what() + ")";
300                 OPENVDB_LOG_WARN("failed to create a temporary copy of " << filename()
301                     << " for delayed loading" << mesg
302                     << "; will read directly from " << filename() << " instead");
303             }
304         }
305 
306         // While the file is open, its mapping, stream buffer and stream
307         // must all be maintained.  Once the file is closed, the buffer and
308         // the stream can be discarded, but the mapping needs to persist
309         // if any grids were lazily loaded.
310         try {
311             newFileMapping.reset(new MappedFile(fname, /*autoDelete=*/isTempFile));
312             newStreamBuf = newFileMapping->createBuffer();
313             newStream.reset(new std::istream(newStreamBuf.get()));
314         } catch (std::exception& e) {
315             std::ostringstream ostr;
316             ostr << "could not open file " << filename();
317             if (e.what() != nullptr) ostr << " (" << e.what() << ")";
318             OPENVDB_THROW(IoError, ostr.str());
319         }
320     }
321 
322     if (newStream->fail()) {
323         OPENVDB_THROW(IoError, "could not open file " << filename());
324     }
325 
326     // Read in the file header.
327     bool newFile = false;
328     try {
329         newFile = Archive::readHeader(*newStream);
330     } catch (IoError& e) {
331         if (e.what() && std::string("not a VDB file") == e.what()) {
332             // Rethrow, adding the filename.
333             OPENVDB_THROW(IoError, filename() << " is not a VDB file");
334         }
335         throw;
336     }
337 
338     mImpl->mFileMapping = newFileMapping;
339     if (mImpl->mFileMapping) mImpl->mFileMapping->setNotifier(notifier);
340     mImpl->mStreamBuf = newStreamBuf;
341     mImpl->mInStream.swap(newStream);
342 
343     // Tag the input stream with the file format and library version numbers
344     // and other metadata.
345     mImpl->mStreamMetadata.reset(new StreamMetadata);
346     mImpl->mStreamMetadata->setSeekable(true);
347     io::setStreamMetadataPtr(inputStream(), mImpl->mStreamMetadata, /*transfer=*/false);
348     Archive::setFormatVersion(inputStream());
349     Archive::setLibraryVersion(inputStream());
350     Archive::setDataCompression(inputStream());
351     io::setMappedFilePtr(inputStream(), mImpl->mFileMapping);
352 
353     // Read in the VDB metadata.
354     mImpl->mMeta = MetaMap::Ptr(new MetaMap);
355     mImpl->mMeta->readMeta(inputStream());
356 
357     if (!inputHasGridOffsets()) {
358         OPENVDB_LOG_DEBUG_RUNTIME("file " << filename() << " does not support partial reading");
359 
360         mImpl->mGrids.reset(new GridPtrVec);
361         mImpl->mNamedGrids.clear();
362 
363         // Stream in the entire contents of the file and append all grids to mGrids.
364         const int32_t gridCount = readGridCount(inputStream());
365         for (int32_t i = 0; i < gridCount; ++i) {
366             GridDescriptor gd;
367             gd.read(inputStream());
368 
369             GridBase::Ptr grid = createGrid(gd);
370             Archive::readGrid(grid, gd, inputStream());
371 
372             gridDescriptors().insert(std::make_pair(gd.gridName(), gd));
373             mImpl->mGrids->push_back(grid);
374             mImpl->mNamedGrids[gd.uniqueName()] = grid;
375         }
376         // Connect instances (grids that share trees with other grids).
377         for (NameMapCIter it = gridDescriptors().begin(); it != gridDescriptors().end(); ++it) {
378             Archive::connectInstance(it->second, mImpl->mNamedGrids);
379         }
380     } else {
381         // Read in just the grid descriptors.
382         readGridDescriptors(inputStream());
383     }
384 
385     mImpl->mIsOpen = true;
386     return newFile; // true if file is not identical to opened file
387 }
388 
389 
390 void
close()391 File::close()
392 {
393     // Reset all data.
394     mImpl->mMeta.reset();
395     mImpl->mGridDescriptors.clear();
396     mImpl->mGrids.reset();
397     mImpl->mNamedGrids.clear();
398     mImpl->mInStream.reset();
399     mImpl->mStreamBuf.reset();
400     mImpl->mStreamMetadata.reset();
401     mImpl->mFileMapping.reset();
402 
403     mImpl->mIsOpen = false;
404     setInputHasGridOffsets(true);
405 }
406 
407 
408 ////////////////////////////////////////
409 
410 
411 bool
hasGrid(const Name & name) const412 File::hasGrid(const Name& name) const
413 {
414     if (!isOpen()) {
415         OPENVDB_THROW(IoError, filename() << " is not open for reading");
416     }
417     return (findDescriptor(name) != gridDescriptors().end());
418 }
419 
420 
421 MetaMap::Ptr
getMetadata() const422 File::getMetadata() const
423 {
424     if (!isOpen()) {
425         OPENVDB_THROW(IoError, filename() << " is not open for reading");
426     }
427     // Return a deep copy of the file-level metadata, which was read
428     // when the file was opened.
429     return MetaMap::Ptr(new MetaMap(*mImpl->mMeta));
430 }
431 
432 
433 GridPtrVecPtr
getGrids() const434 File::getGrids() const
435 {
436     if (!isOpen()) {
437         OPENVDB_THROW(IoError, filename() << " is not open for reading");
438     }
439 
440     GridPtrVecPtr ret;
441     if (!inputHasGridOffsets()) {
442         // If the input file doesn't have grid offsets, then all of the grids
443         // have already been streamed in and stored in mGrids.
444         ret = mImpl->mGrids;
445     } else {
446         ret.reset(new GridPtrVec);
447 
448         Archive::NamedGridMap namedGrids;
449 
450         // Read all grids represented by the GridDescriptors.
451         for (NameMapCIter i = gridDescriptors().begin(), e = gridDescriptors().end(); i != e; ++i) {
452             const GridDescriptor& gd = i->second;
453             GridBase::Ptr grid = readGrid(gd);
454             ret->push_back(grid);
455             namedGrids[gd.uniqueName()] = grid;
456         }
457 
458         // Connect instances (grids that share trees with other grids).
459         for (NameMapCIter i = gridDescriptors().begin(), e = gridDescriptors().end(); i != e; ++i) {
460             Archive::connectInstance(i->second, namedGrids);
461         }
462     }
463     return ret;
464 }
465 
466 
467 GridBase::Ptr
retrieveCachedGrid(const Name & name) const468 File::retrieveCachedGrid(const Name& name) const
469 {
470     // If the file has grid offsets, grids are read on demand
471     // and not cached in mNamedGrids.
472     if (inputHasGridOffsets()) return GridBase::Ptr();
473 
474     // If the file does not have grid offsets, mNamedGrids should already
475     // contain the entire contents of the file.
476 
477     // Search by unique name.
478     Archive::NamedGridMap::const_iterator it =
479         mImpl->mNamedGrids.find(GridDescriptor::stringAsUniqueName(name));
480     // If not found, search by grid name.
481     if (it == mImpl->mNamedGrids.end()) it = mImpl->mNamedGrids.find(name);
482     if (it == mImpl->mNamedGrids.end()) {
483         OPENVDB_THROW(KeyError, filename() << " has no grid named \"" << name << "\"");
484     }
485     return it->second;
486 }
487 
488 
489 ////////////////////////////////////////
490 
491 
492 GridPtrVecPtr
readAllGridMetadata()493 File::readAllGridMetadata()
494 {
495     if (!isOpen()) {
496         OPENVDB_THROW(IoError, filename() << " is not open for reading");
497     }
498 
499     GridPtrVecPtr ret(new GridPtrVec);
500 
501     if (!inputHasGridOffsets()) {
502         // If the input file doesn't have grid offsets, then all of the grids
503         // have already been streamed in and stored in mGrids.
504         for (size_t i = 0, N = mImpl->mGrids->size(); i < N; ++i) {
505             // Return copies of the grids, but with empty trees.
506             ret->push_back((*mImpl->mGrids)[i]->copyGridWithNewTree());
507         }
508     } else {
509         // Read just the metadata and transforms for all grids.
510         for (NameMapCIter i = gridDescriptors().begin(), e = gridDescriptors().end(); i != e; ++i) {
511             const GridDescriptor& gd = i->second;
512             GridBase::ConstPtr grid = readGridPartial(gd, /*readTopology=*/false);
513             // Return copies of the grids, but with empty trees.
514             // (As of 0.98.0, at least, it would suffice to just const cast
515             // the grid pointers returned by readGridPartial(), but shallow
516             // copying the grids helps to ensure future compatibility.)
517             ret->push_back(grid->copyGridWithNewTree());
518         }
519     }
520     return ret;
521 }
522 
523 
524 GridBase::Ptr
readGridMetadata(const Name & name)525 File::readGridMetadata(const Name& name)
526 {
527     if (!isOpen()) {
528         OPENVDB_THROW(IoError, filename() << " is not open for reading.");
529     }
530 
531     GridBase::ConstPtr ret;
532     if (!inputHasGridOffsets()) {
533         // Retrieve the grid from mGrids, which should already contain
534         // the entire contents of the file.
535         ret = readGrid(name);
536     } else {
537         NameMapCIter it = findDescriptor(name);
538         if (it == gridDescriptors().end()) {
539             OPENVDB_THROW(KeyError, filename() << " has no grid named \"" << name << "\"");
540         }
541 
542         // Seek to and read in the grid from the file.
543         const GridDescriptor& gd = it->second;
544         ret = readGridPartial(gd, /*readTopology=*/false);
545     }
546     return ret->copyGridWithNewTree();
547 }
548 
549 
550 ////////////////////////////////////////
551 
552 
553 GridBase::Ptr
readGrid(const Name & name)554 File::readGrid(const Name& name)
555 {
556     return readGridByName(name, BBoxd());
557 }
558 
559 
560 GridBase::Ptr
readGrid(const Name & name,const BBoxd & bbox)561 File::readGrid(const Name& name, const BBoxd& bbox)
562 {
563     return readGridByName(name, bbox);
564 }
565 
566 
567 GridBase::Ptr
readGridByName(const Name & name,const BBoxd & bbox)568 File::readGridByName(const Name& name, const BBoxd& bbox)
569 {
570     if (!isOpen()) {
571         OPENVDB_THROW(IoError, filename() << " is not open for reading.");
572     }
573 
574     const bool clip = bbox.isSorted();
575 
576     // If a grid with the given name was already read and cached
577     // (along with the entire contents of the file, because the file
578     // doesn't support random access), retrieve and return it.
579     GridBase::Ptr grid = retrieveCachedGrid(name);
580     if (grid) {
581         if (clip) {
582             grid = grid->deepCopyGrid();
583             grid->clipGrid(bbox);
584         }
585         return grid;
586     }
587 
588     NameMapCIter it = findDescriptor(name);
589     if (it == gridDescriptors().end()) {
590         OPENVDB_THROW(KeyError, filename() << " has no grid named \"" << name << "\"");
591     }
592 
593     // Seek to and read in the grid from the file.
594     const GridDescriptor& gd = it->second;
595     grid = (clip ? readGrid(gd, bbox) : readGrid(gd));
596 
597     if (gd.isInstance()) {
598         /// @todo Refactor to share code with Archive::connectInstance()?
599         NameMapCIter parentIt =
600             findDescriptor(GridDescriptor::nameAsString(gd.instanceParentName()));
601         if (parentIt == gridDescriptors().end()) {
602             OPENVDB_THROW(KeyError, "missing instance parent \""
603                 << GridDescriptor::nameAsString(gd.instanceParentName())
604                 << "\" for grid " << GridDescriptor::nameAsString(gd.uniqueName())
605                 << " in file " << filename());
606         }
607 
608         GridBase::Ptr parent;
609         if (clip) {
610             const CoordBBox indexBBox = grid->constTransform().worldToIndexNodeCentered(bbox);
611             parent = readGrid(parentIt->second, indexBBox);
612         } else {
613             parent = readGrid(parentIt->second);
614         }
615         if (parent) grid->setTree(parent->baseTreePtr());
616     }
617     return grid;
618 }
619 
620 
621 ////////////////////////////////////////
622 
623 
624 void
writeGrids(const GridCPtrVec & grids,const MetaMap & meta) const625 File::writeGrids(const GridCPtrVec& grids, const MetaMap& meta) const
626 {
627     if (isOpen()) {
628         OPENVDB_THROW(IoError,
629             filename() << " cannot be written because it is open for reading");
630     }
631 
632     // Create a file stream and write it out.
633     std::ofstream file;
634     file.open(filename().c_str(),
635         std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
636 
637     if (file.fail()) {
638         OPENVDB_THROW(IoError, "could not open " << filename() << " for writing");
639     }
640 
641     // Write out the vdb.
642     Archive::write(file, grids, /*seekable=*/true, meta);
643 
644     file.close();
645 }
646 
647 
648 ////////////////////////////////////////
649 
650 
651 void
readGridDescriptors(std::istream & is)652 File::readGridDescriptors(std::istream& is)
653 {
654     // This method should not be called for files that don't contain grid offsets.
655     assert(inputHasGridOffsets());
656 
657     gridDescriptors().clear();
658 
659     for (int32_t i = 0, N = readGridCount(is); i < N; ++i) {
660         // Read the grid descriptor.
661         GridDescriptor gd;
662         gd.read(is);
663 
664         // Add the descriptor to the dictionary.
665         gridDescriptors().insert(std::make_pair(gd.gridName(), gd));
666 
667         // Skip forward to the next descriptor.
668         gd.seekToEnd(is);
669     }
670 }
671 
672 
673 ////////////////////////////////////////
674 
675 
676 File::NameMapCIter
findDescriptor(const Name & name) const677 File::findDescriptor(const Name& name) const
678 {
679     const Name uniqueName = GridDescriptor::stringAsUniqueName(name);
680 
681     // Find all descriptors with the given grid name.
682     std::pair<NameMapCIter, NameMapCIter> range = gridDescriptors().equal_range(name);
683 
684     if (range.first == range.second) {
685         // If no descriptors were found with the given grid name, the name might have
686         // a suffix ("name[N]").  In that case, remove the "[N]" suffix and search again.
687         range = gridDescriptors().equal_range(GridDescriptor::stripSuffix(uniqueName));
688     }
689 
690     const size_t count = size_t(std::distance(range.first, range.second));
691     if (count > 1 && name == uniqueName) {
692         OPENVDB_LOG_WARN(filename() << " has more than one grid named \"" << name << "\"");
693     }
694 
695     NameMapCIter ret = gridDescriptors().end();
696 
697     if (count > 0) {
698         if (name == uniqueName) {
699             // If the given grid name is unique or if no "[N]" index was given,
700             // use the first matching descriptor.
701             ret = range.first;
702         } else {
703             // If the given grid name has a "[N]" index, find the descriptor
704             // with a matching unique name.
705             for (NameMapCIter it = range.first; it != range.second; ++it) {
706                 const Name candidateName = it->second.uniqueName();
707                 if (candidateName == uniqueName || candidateName == name) {
708                     ret = it;
709                     break;
710                 }
711             }
712         }
713     }
714     return ret;
715 }
716 
717 
718 ////////////////////////////////////////
719 
720 
721 GridBase::Ptr
createGrid(const GridDescriptor & gd) const722 File::createGrid(const GridDescriptor& gd) const
723 {
724     // Create the grid.
725     if (!GridBase::isRegistered(gd.gridType())) {
726         OPENVDB_THROW(KeyError, "Cannot read grid "
727             << GridDescriptor::nameAsString(gd.uniqueName())
728             << " from " << filename() << ": grid type "
729             << gd.gridType() << " is not registered");
730     }
731 
732     GridBase::Ptr grid = GridBase::createGrid(gd.gridType());
733     if (grid) grid->setSaveFloatAsHalf(gd.saveFloatAsHalf());
734 
735     return grid;
736 }
737 
738 
739 GridBase::ConstPtr
readGridPartial(const GridDescriptor & gd,bool readTopology) const740 File::readGridPartial(const GridDescriptor& gd, bool readTopology) const
741 {
742     // This method should not be called for files that don't contain grid offsets.
743     assert(inputHasGridOffsets());
744 
745     GridBase::Ptr grid = createGrid(gd);
746 
747     // Seek to grid.
748     gd.seekToGrid(inputStream());
749 
750     // Read the grid partially.
751     readGridPartial(grid, inputStream(), gd.isInstance(), readTopology);
752 
753     // Promote to a const grid.
754     GridBase::ConstPtr constGrid = grid;
755 
756     return constGrid;
757 }
758 
759 
760 GridBase::Ptr
readGrid(const GridDescriptor & gd) const761 File::readGrid(const GridDescriptor& gd) const
762 {
763     return Impl::readGrid(*this, gd, Impl::NoBBox());
764 }
765 
766 
767 GridBase::Ptr
readGrid(const GridDescriptor & gd,const BBoxd & bbox) const768 File::readGrid(const GridDescriptor& gd, const BBoxd& bbox) const
769 {
770     return Impl::readGrid(*this, gd, bbox);
771 }
772 
773 
774 GridBase::Ptr
readGrid(const GridDescriptor & gd,const CoordBBox & bbox) const775 File::readGrid(const GridDescriptor& gd, const CoordBBox& bbox) const
776 {
777     return Impl::readGrid(*this, gd, bbox);
778 }
779 
780 
781 void
readGridPartial(GridBase::Ptr grid,std::istream & is,bool isInstance,bool readTopology) const782 File::readGridPartial(GridBase::Ptr grid, std::istream& is,
783     bool isInstance, bool readTopology) const
784 {
785     // This method should not be called for files that don't contain grid offsets.
786     assert(inputHasGridOffsets());
787 
788     // This code needs to stay in sync with io::Archive::readGrid(), in terms of
789     // the order of operations.
790     readGridCompression(is);
791     grid->readMeta(is);
792 
793     // drop DelayedLoadMetadata from the grid as it is only useful for IO
794     if ((*grid)[GridBase::META_FILE_DELAYED_LOAD]) {
795         grid->removeMeta(GridBase::META_FILE_DELAYED_LOAD);
796     }
797 
798     if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_GRID_INSTANCING) {
799         grid->readTransform(is);
800         if (!isInstance && readTopology) {
801             grid->readTopology(is);
802         }
803     } else {
804         if (readTopology) {
805             grid->readTopology(is);
806             grid->readTransform(is);
807         }
808     }
809 }
810 
811 
812 ////////////////////////////////////////
813 
814 
815 File::NameIterator
beginName() const816 File::beginName() const
817 {
818     if (!isOpen()) {
819         OPENVDB_THROW(IoError, filename() << " is not open for reading");
820     }
821     return File::NameIterator(gridDescriptors().begin());
822 }
823 
824 
825 File::NameIterator
endName() const826 File::endName() const
827 {
828     return File::NameIterator(gridDescriptors().end());
829 }
830 
831 } // namespace io
832 } // namespace OPENVDB_VERSION_NAME
833 } // namespace openvdb
834