io (lx_io.hpp)

From The Foundry MODO SDK wiki
Jump to: navigation, search
There are security restrictions on this page


Contents

General I/O

These interfaces provide a set of common objects for doing all types of input and output. Raw streams provide a low-level way of performing serial I/O on a variety of different substrates. Block streams add more structure to simple linear I/O, allowing data to be more formally defined. IFF format streams are a special case of block format, supporting a more rigid structure and larger overall size.

Loaders and savers define the extensible architecture for importing and exporting objects of all types. Objects may be loaded opaquely so they are implemented externally, or transparently so they are implemented by built-in nexus objects.

These are error codes for this subsystem and all other I/O-related subsystems.

(1) SDK: Declarations

Block Stream Interfaces

Block stream interfaces support block-structured I/O with multiple data types. There is an interface for reading and one for writing.

(2) SDK: Declarations
 #define LXu_BLOCKREAD           "36A00A47-2664-49E0-BAFF-263E638532B5"
 #define LXu_BLOCKWRITE          "ADE569E1-E9A1-4AD6-B625-634093660965"

Block streams are also style-transparent, reading and writing using the same API for both text and binary streams. As result, blocks have to be identified by both a binary ID code and a text string. Internally, the binary ID code value is always used.

(3) SDK: LXtBlockHeader struct
 LXtID4                   id;
 const char              *token;

For string reading, there are two modes - FORCE and RAW. Force will raise an exception if the string is not found. If not set, however, the function will return true if any string is read, including the empty string. RAW causes the stream to be read as a literal string, ignoring quotes and reading to eol.

(4) SDK: Declarations
 #define LXfBLKS_FORCE    (1<<0)
 #define LXfBLKS_RAW      (1<<1)

Function descriptions are not provided since these objects are not commonly encountered in nexus file I/O. TODO: fill these in.

(5) SDK: ILxBlockRead interface
         LXxMETHOD( LxResult,
 FindBlock) (
         LXtObjectID              self,
         const LXtBlockHeader    *head,
         int                      flags,
         LXtID4                  *blkId);
 
         LXxMETHOD( void,
 End) (
         LXtObjectID              self);
 
         LXxMETHOD( int,
 Depth) (
         LXtObjectID              self);
 
         LXxMETHOD( LxResult,
 ReadI1) (
         LXtObjectID              self,
         char                    *data,
         int                      count,
         int                     *ocount);
 
         LXxMETHOD( LxResult,
 ReadI2) (
         LXtObjectID              self,
         short                   *data,
         int                      count,
         int                     *ocount);
 
         LXxMETHOD( LxResult,
 ReadI4) (
         LXtObjectID              self,
         int                     *data,
         int                      count,
         int                     *ocount);
 
         LXxMETHOD( LxResult,
 ReadU1) (
         LXtObjectID              self,
         unsigned char           *data,
         int                      count,
         int                     *ocount);
 
         LXxMETHOD( LxResult,
 ReadU2) (
         LXtObjectID              self,
         unsigned short          *data,
         int                      count,
         int                     *ocount);
 
         LXxMETHOD( LxResult,
 ReadU4) (
         LXtObjectID              self,
         unsigned int            *data,
         int                      count,
         int                     *ocount);
 
         LXxMETHOD( LxResult,
 ReadFP) (
         LXtObjectID              self,
         float                   *data,
         int                      count,
         int                     *ocount);
 
         LXxMETHOD( LxResult,
 ReadFP8) (
         LXtObjectID              self,
         double                  *data,
         int                      count,
         int                     *ocount);

(6) SDK: Declarations
 

Reading a string takes a buffer for the result and its size. If FORCE is true this will return an error if no string is found. If PARTIAL is true then as much string as can be found will be read into the buffer and LXe_IO_PARTIALSTRING will be returned if there is more left to read. Otherwise the entire string will always be read and LXe_IO_TRUNCATED will be returned if some of the string had to be discarded. The returned count is the number of characters read including the terminating null.

(7) SDK: ILxBlockRead interface
         LXxMETHOD( LxResult,
 ReadString) (
         LXtObjectID              self,
         char                    *buf,
         int                      max,
         int                      flags,
         int                     *ocount);

(8) SDK: Declarations
 #define LXfREADSTRING_FORCE     0x01
 #define LXfREADSTRING_RAW       0x02
 #define LXfREADSTRING_PARTIAL   0x04

(9) SDK: ILxBlockRead interface
         LXxMETHOD( LxResult,
 ReadIDCode) (
         LXtObjectID              self,
         const LXtBlockHeader    *list,
         LXtID4                  *idCode);
 
         LXxMETHOD( LxResult,
 ReadVX) (
         LXtObjectID              self,
         unsigned                *data,
         int                      count,
         int                     *ocount);

ReadString converts a text encoding of string to another text encoding given by the following methods using ILxTextEncoding service. The source encoding is the encoding for strings to read. The target encoding is for a string set to "buf" of ReadString. The text encoding convertion does not work with PARTIAL mode. The returned count is the number of characters read including the terminating null based on the source string.

(10) SDK: ILxBlockRead interface
         LXxMETHOD(  LxResult,
 SetSourceEncoding) (
         LXtObjectID              self,
         unsigned                 encoding);
 
         LXxMETHOD(  LxResult,
 SetTargetEncoding) (
         LXtObjectID              self,
         unsigned                 encoding);

User class methods are relatively more C++ friendly, using argument types and throwing exceptions on errors. The read count is discarded for these methods because they require reading.

(11) User Class: BlockRead method
         bool
 Read (
         short                   *val,
         bool                     force = true)
 {
         int                      count;
         LxResult                 rc;
 
         rc = ReadI2 (val, (force ? 1 : -1), &count);
         if (LXx_FAIL (rc))
                 throw (rc);
 
         return (count == 1);
 }
 
         bool
 Read (
         unsigned short          *val,
         bool                     force = true)
 {
         int                      count;
         LxResult                 rc;
 
         rc = ReadU2 (val, (force ? 1 : -1), &count);
         if (LXx_FAIL (rc))
                 throw (rc);
 
         return (count == 1);
 }
 
         bool
 Read (
         int                     *val,
         bool                     force = true)
 {
         int                      count;
         LxResult                 rc;
 
         rc = ReadI4 (val, (force ? 1 : -1), &count);
         if (LXx_FAIL (rc))
                 throw (rc);
 
         return (count == 1);
 }
 
         bool
 Read (
         unsigned int            *val,
         bool                     force = true)
 {
         int                      count;
         LxResult                 rc;
 
         rc = ReadU4 (val, (force ? 1 : -1), &count);
         if (LXx_FAIL (rc))
                 throw (rc);
 
         return (count == 1);
 }
 
         bool
 Read (
         float                   *val,
         bool                     force = true)
 {
         int                      count;
         LxResult                 rc;
 
         rc = ReadFP (val, (force ? 1 : -1), &count);
         if (LXx_FAIL (rc))
                 throw (rc);
 
         return (count == 1);
 }
 
         bool
 Read (
         double                  *val,
         bool                     force = true)
 {
         int                      count;
         LxResult                 rc;
 
         rc = ReadFP8 (val, (force ? 1 : -1), &count);
         if (LXx_FAIL (rc))
                 throw (rc);
 
         return (count == 1);
 }

Reading strings in C++ does partial reads to build up an STL string. If force is true it will raise an exception if the string is not found. Otherwise no string and a valid empty string will both return false. The terminal string has a final null, which we don't want included in the STL string.

(12) User Class: BlockRead method
         bool
 Read (
         std::string             &result,
         bool                     force = true)
 {
         char                     buf[128];
         LxResult                 rc;
         int                      flag, got;
 
         result = "";
         flag = LXfREADSTRING_PARTIAL | (force ? LXfREADSTRING_FORCE : 0);
 
         while (1) {
                 rc = ReadString (buf, sizeof (buf), flag, &got);
                 if (LXx_FAIL (rc))
                         throw (rc);
 
                 if (rc == LXe_IO_PARTIALSTRING)
                         result.append (buf, (size_t) got);
                 else {
                         if (got)
                                 result.append (buf, (size_t) got - 1);
 
                         break;
                 }
         }
 
         return !result.empty ();
 }

Empty BlockRead Python user class.

(13) PY: BlockRead method
 pass

The write methods are very similar to the read methods. Function descriptions are not provided since these objects are not commonly encountered in nexus file I/O. TODO: fill these in.

LEAF blocks can contain no sub-blocks. PARAM blocks are leaf blocks written at the start of a non-leaf block. In XML they are written into the block header, but otherwise are identical to normal blocks.

(14) SDK: Declarations
 #define LXfBLKW_LEAF     (1<<0)
 #define LXfBLKW_PARAM    (1<<1)

(15) SDK: ILxBlockWrite interface
         LXxMETHOD( LxResult,
 WriteBlock) (
         LXtObjectID              self,
         const LXtBlockHeader    *head,
         int                      flags);
 
         LXxMETHOD( void,
 End) (
         LXtObjectID              self);
 
         LXxMETHOD( int,
 Depth) (
         LXtObjectID              self);
 
         LXxMETHOD( LxResult,
 WriteI1) (
         LXtObjectID              self,
         const char              *data,
         int                      count);
 
         LXxMETHOD( LxResult,
 WriteI2) (
         LXtObjectID              self,
         const short             *data,
         int                      count);
 
         LXxMETHOD( LxResult,
 WriteI4) (
         LXtObjectID              self,
         const int               *data,
         int                      count);
 
         LXxMETHOD( LxResult,
 WriteU1) (
         LXtObjectID              self,
         const unsigned char     *data,
         int                      count);
 
         LXxMETHOD( LxResult,
 WriteU2) (
         LXtObjectID              self,
         const unsigned short    *data,
         int                      count);
 
         LXxMETHOD( LxResult,
 WriteU4) (
         LXtObjectID              self,
         const unsigned int      *data,
         int                      count);
 
         LXxMETHOD( LxResult,
 WriteFP) (
         LXtObjectID              self,
         const float             *data,
         int                      count);
 
         LXxMETHOD( LxResult,
 WriteFP8) (
         LXtObjectID              self,
         const double            *data,
         int                      count);
 
         LXxMETHOD( LxResult,
 WriteString) (
         LXtObjectID              self,
         const char              *str);
 
         LXxMETHOD( LxResult,
 WriteIDCode) (
         LXtObjectID              self,
         const LXtBlockHeader    *ident);
 
         LXxMETHOD( LxResult,
 WriteVX) (
         LXtObjectID              self,
         const unsigned int      *data,
         int                      count);

WriteString converts a text encoding of string to another text encoding given by the following methods using ILxTextEncoding service. The source encoding is for a string set to "buf" of WriteString. The target encoding is the encoding for strings to write.

(16) SDK: ILxBlockWrite interface
         LXxMETHOD(  LxResult,
 SetSourceEncoding) (
         LXtObjectID              self,
         unsigned                 encoding);
 
         LXxMETHOD(  LxResult,
 SetTargetEncoding) (
         LXtObjectID              self,
         unsigned                 encoding);

User class methods are relatively more C++ friendly, using argument types and throwing exceptions on errors.

(17) User Class: BlockWrite method
         void
 Write (
         const short              val)
 {
         LxResult rc = WriteI2 (&val, 1);
         if (LXx_FAIL (rc))
                 throw (rc);
 }
 
         void
 Write (
         const unsigned short     val)
 {
         LxResult rc = WriteU2 (&val, 1);
         if (LXx_FAIL (rc))
                 throw (rc);
 }
 
         void
 Write (
         const int                val)
 {
         LxResult rc = WriteI4 (&val, 1);
         if (LXx_FAIL (rc))
                 throw (rc);
 }
 
         void
 Write (
         const unsigned int       val)
 {
         LxResult rc = WriteU4 (&val, 1);
         if (LXx_FAIL (rc))
                 throw (rc);
 }
 
         void
 Write (
         const float              val)
 {
         LxResult rc = WriteFP (&val, 1);
         if (LXx_FAIL (rc))
                 throw (rc);
 }
 
         void
 Write (
         const double             val)
 {
         LxResult rc = WriteFP8 (&val, 1);
         if (LXx_FAIL (rc))
                 throw (rc);
 }
 
         void
 Write (
         const char              *val)
 {
         LxResult rc = WriteString (val);
         if (LXx_FAIL (rc))
                 throw (rc);
 }
 
         void
 Write (
         const std::string       &val)
 {
         LxResult rc = WriteString (val.c_str ());
         if (LXx_FAIL (rc))
                 throw (rc);
 }

Empty BlockWrite Python user class.

(18) PY: BlockWrite method
 pass

Loaders

An ILxLoader interface is an extensible server for loading objects of many different classes. Methods on the loader interface are called in sequence. Specifically Recognize() is called to open a file, and Cleanup() is called to close it. Recognize() is given the filename and LoadInfo object, and it should return OK if the file can be loaded by this loader.

(19) SDK: ILxLoader interface
         LXxMETHOD( LxResult,
 Recognize) (
         LXtObjectID              self,
         const char              *filename,
         LXtObjectID              loadInfo);

The LoaderInfo interface can be used to read the context of the load and to set the attributes that apply to this file. For loaders that can load more than one object type, TestClass() returns LXe_TRUE for classes that the client wants to accept. The priority for each type can be used to decide which one to accept, where lower is better.

(20) SDK: ILxLoaderInfo interface
         LXxMETHOD( LxResult,
 TestClass) (
         LXtObjectID              self,
         const LXtGUID           *clsGUID,
         unsigned                *priority);

During recognition the loader can set information about a file that it's capable of loading. All of this is optional. The class is the object type for loaders that can load more than one type. Flags can suggest opaque versus direct load and if the format has options. The format can be set if a different saver should be used for this object type.

(21) SDK: ILxLoaderInfo interface
         LXxMETHOD( LxResult,
 SetClass) (
         LXtObjectID              self,
         const LXtGUID           *clsGUID);
 
         LXxMETHOD( LxResult,
 SetFlags) (
         LXtObjectID              self,
         unsigned                 flags);
 
         LXxMETHOD( LxResult,
 SetFormat) (
         LXtObjectID              self,
         const char              *format);

(22) SDK: Declarations
 #define LXfLOAD_OPAQUE           0x01
 #define LXfLOAD_OPTIONS          0x02

The loader can also set other data by querying for additional interfaces based on object type. This is useful when doing a direct load to specify the attributes of the object to contain the data. For instance to do a direct load of an image the loader would query for ImageLoaderTarget and use its methods to set the pixel format and size of the destination image. The LoadInfo object can also be queried for a monitor.

After recognition, the LoadInstance() method may be called to load opaque objects, in which case the new COM object should be returned in the void pointer. For non-opaque loads the (perhaps confusingly named) LoadObject() method may be called to load the data into the destination object.

(23) SDK: ILxLoader interface
         LXxMETHOD( LxResult,
 LoadInstance) (
         LXtObjectID              self,
         LXtObjectID              loadInfo,
         LXtObjectID              monitor,
         void                   **ppvObj);
 
         LXxMETHOD( LxResult,
 LoadObject) (
         LXtObjectID              self,
         LXtObjectID              loadInfo,
         LXtObjectID              monitor,
         LXtObjectID              dest);

After any attempt at recognition Cleanup() will be called to allow the loader to close the file and reset its state.

(24) SDK: ILxLoader interface
         LXxMETHOD( void,
 Cleanup) (
         LXtObjectID              self);

This method creates an option object for this loader. This should have a StreamIO interface to allow it to be saved and loaded.

(25) SDK: ILxLoader interface
         LXxMETHOD( LxResult,
 SpawnOptions) (
         LXtObjectID              self,
         void                   **ppvObj);

(26) SDK: Declarations
 #define LXu_LOADERINFO  "4CA8BE1A-6ADE-4F93-99F6-1F0EFC8A581E"
 #define LXa_LOADERINFO  "loaderinfo" 
 #define LXu_LOADER      "7711F608-B8FF-48BF-81ED-CEBDE54D34DE"
 #define LXa_LOADER      "loader2"

Tags define the classes this loader server supports and hints about the files that it might be able to process.

CLASSLIST
a string of class aliases or GUIDs separated by spaces, such as "image animation". These are the classes which this loader can support.
DOSPATTERN
a pattern string for filenames in Microsoft Windows (really DOS) format. This is typically something like "*.avi;*.jpg".
MACPATTERN
a pattern string for Macintosh filetypes that might be read with this loader. This is typically something like "MooV;TTXT".
THREADSAFE
indicates whether the loader is threadsafe (i.e. multiple loaders of the same type can be executed at once). Values other than "Yes" are all interpreted as no.

(27) SDK: Declarations
 #define LXsLOD_CLASSLIST        "loader.classList"
 #define LXsLOD_DOSPATTERN       "loader.dosPattern"
 #define LXsLOD_MACPATTERN       "loader.macPattern"
 #define LXsLOD_THREADSAFE       "loader.threadsafe"

(28) User Class: LoaderInfo method

(29) User Class: Loader method

Empty LoaderInfo Python user class.

(30) PY: LoaderInfo method
 pass

Empty Loader Python user class.

(31) PY: Loader method
 pass

One general class GUID for loaders is the "side-effect" object class. A loader which loads a side-effect object doesn't really load anything, it just wants a chance to parse a file and do something. There is no ILxSideEffect interface.

(32) SDK: Declarations
 #define LXu_SIDEEFFECT  "3414D56B-31DE-47C7-B751-092B51591DD2"
 #define LXa_SIDEEFFECT  "sideeffect"

Saver

An ILxSaver interface knows how to save objects of a specific type. This is a very much simpler interface and just has a single function to perform the save to the given file using the given monitor. The Verify method allows the plugin to inform the user if parts of his scene will not be saved. The source is the object being saved.

(33) SDK: ILxSaver interface
         LXxMETHOD( LxResult,
 Verify) (
         LXtObjectID              self,
         LXtObjectID              source,
         LXtObjectID              message);
 
         LXxMETHOD( LxResult,
 Save) (
         LXtObjectID              self,
         LXtObjectID              source,
         const char              *filename,
         LXtObjectID              monitor);

(34) SDK: Declarations
 #define LXu_SAVER       "75AD8F36-5B69-413b-A77B-5A78D39AEF51"
 #define LXa_SAVER       "saver"

Tags define the single class and file type.

OUTCLASS
the class alias or GUID for objects that can be saved. ("image")
DOSTYPE
the three-character DOS file extension that should be used by default. ("JPG")
MACTYPE
the four-character Macintosh type code that should be applied to the new file. ("PICT")
OVERWRITE
indicates that the saver needs to original file in place in order to save changes into it. This disables any safety mechanism that renames the original file to prevent save errors from destroying data. Savers that set this tag to "1" are expected to handle save errors gracefully on their own.
SAVEAS
indicates that the saver can be used in the Save As dialog. Save As will allow only savers that have appropriate loader to be listed and it's comparing saver and loader by using server name. If you have named loader and saver for the same file format differently, then you can use this flag in order to get your saver to be displayed in the Save As dialog.

(35) SDK: Declarations
 #define LXsSAV_OUTCLASS         "saver.outClass"
 #define LXsSAV_DOSTYPE          "saver.dosType"
 #define LXsSAV_MACTYPE          "saver.macType"
 #define LXsSAV_OVERWRITE        "saver.overwrite"
 #define LXsSAV_SAVEAS           "saver.saveAs"

(36) User Class: Saver method

Empty Saver Python user class.

(37) PY: Saver method
 pass

Monitors

An ILxMonitor interface allows progress tracking. The monitor is initialized with a max count, and each stage of processing increments the count by something.

Initialize() takes the total number of steps the monitor has, and returns a result code. It is possible that the monitor will be immediately aborted, so you should check for LXe_ABORT codes from this call.

(38) SDK: ILxMonitor interface
         LXxMETHOD( LxResult,
 Initialize) (
         LXtObjectID              self,
         unsigned int             count);

This steps the monitor by the count provided. Care should be taken to keep from exceeding the total number of steps set with Initialize(). Of the user has aborted the operation, this method will return LXe_ABORT and the client should stop the operation. Commands normally set the LXe_ABORT code on their message object so that the caller knows that the command has been aborted by the user.

(39) SDK: ILxMonitor interface
         LXxMETHOD( LxResult,
 Increment) (
         LXtObjectID              self,
         unsigned int             count);

These macros support calling the function vectors on monitor objects. If the monitor pointer is null, these do no harm. Init returns zero on failure and incr/step returns zero to continue. The Increment() method can be called directly to determine if it returned a failure code or LXe_ABORT.

The most useful macros here are:

INIT()
Returns true if the monitor was successfully initialized. This may return false to indicate that that the monitor was aborted as soon as it was opened, usually because it wound up being a sub-monitor of another monitor.
INCR()
Increment a monitor by the given number of steps. Returns true if the user aborted, and false if it is still running.
STEP()
Increment the monitor by one step. Returns true if the user aborted.
TEST()
Returns true if the user aborted, and false if it is still running. Under the hood, this is simply calling INCR() with a step of 0, and will check for user input before returning. If you are running a long but unbounded task where you can't step the monitor, this will allow you to check for aborts without the app appearing to be locked.
STATUS()
Same as TEST(), but returns an LxResult code of LXe_ABORT if the user aborted, and LXe_OK if the monitor is still running.

(40) SDK: Declarations
 #define LXu_MONITOR             "2B514D4C-5142-4687-BCEF-B0FD44A33146"
 #define LXa_MONITOR             "monitor" 
 #define LXxMON_CALL(m,fn,k)     ((m[0]->fn)(m,k))
 #define LXxMON_OCAL(m,fn,k)     (m ? LXxMON_CALL(m,fn,k) : LXe_OK)
 
 #define LXxMON_INIT(m,n)        LXx_OK  (LXxMON_OCAL(m,Initialize,n))
 #define LXxMON_INCR(m,i)        LXx_FAIL(LXxMON_OCAL(m,Increment, i))
 #define LXxMON_STEP(m)          LXxMON_INCR(m,1)
 #define LXxMON_TEST(m)          LXxMON_INCR(m,0)
 #define LXxMON_STATUS(m)        LXxMON_OCAL(m,Increment, 0)

The above macros are made more or less obsolete by the user class, which provides simpler methods which do nothing if there is no interface.

(41) User Class: Monitor method
         bool
 Init (
         unsigned int             count)
 {
         if (test ())
                 return LXx_OK (Initialize (count));
         else
                 return true;
 }
 
         bool
 Step (
         unsigned int             count = 1)
 {
         if (test ())
                 return LXx_FAIL (Increment (count));
         else
                 return false;
 }

Similar methods for Python.

(42) PY: Monitor method
 def Init(self, count):
     if self.test(): self.Initialize(count)
 
 def Step(self):
     if self.test(): self.Increment(1)

Object I/O

An ILxStreamIO interface allows any object to save or load itself on a block stream. The Write() method writes the underlying object to the block stream, while the Read() method reads the data and sets the state of the object to match the one that was written. The caller will have determined that an object of the right type follows in the stream.

(43) SDK: ILxStreamIO interface
         LXxMETHOD( LxResult,
 Write) (
         LXtObjectID              self,
         LXtObjectID              stream);
 
         LXxMETHOD( LxResult,
 Read) (
         LXtObjectID              self,
         LXtObjectID              stream);

(44) SDK: Declarations
 #define LXu_STREAMIO    "2884D6EE-4BA7-46D6-A776-8EE22C9FD414"

Empty StreamIO Python user class.

(45) PY: StreamIO method
 pass