# LScript v2.1 Release Notes

### [ Please Note ]

Due to changes in the plug-in API, LScript v2.1
requires LightWave [6.5] to function properly.

## New Features

      The facilities of the Layout LScript getfirstitem() command have been made obsolete by a
      series of new Object Agent constructors.  The existence of these new constructors deprecates
      the getfirstitem() function, and it will likely be removed from LScript in a later release.

      Five new Object Agent constructor functions have been added to LScript:

            Mesh()
            Camera()
            Image()
            Light()
            Scene()

      Each of these Object Agent constructors returns an Object Agent identical to that of the
      getfirstitem() function.  Each also accepts the same types of arguments as the getfirstitem()
      function, with the exception of the type of the Object Agent (i.e., MESH, CAMERA, etc.).
      Objects can be identified by name (string) or by index (integer).  In addition, you can
      call the constructor without arguments and an Object Agent will be generated for the first
      object of that type found in the system:

            obj = Mesh("Cow");          // access object "Cow"
            obj = Camera(2);            // access 2nd scene camera
            obj = Light();              // work with the first scene light

      As with any Object Agent returned by getfirstitem(), you can iterate over all objects of
      that type using the next() method.

      The main difference between these constructors and getfirstitem() is that the Mesh()
      constructor, and some of the methods and data exported by the Object Agents it generates,
      are now global.  The Mesh() constructor can be called from any LScript, either Modeler
      or Layout.

      In Modeler, an exception exists that allows you to access the currently active mesh.  By
      using an index value of zero (0), an Object Agent will be generated for the currently
      selected mesh object.

            obj = Mesh(0);              // gimme the current object

      The new methods avaialble from the Mesh() Object Agent in all environments (except where
      noted) are:

            pointCount([<layer>])

                This method reports the total number of points
                in the Mesh object, or optionally in an individual
                layer of Mesh (use the new 'totallayers' data member
                to determine the valid layers in the object).

            polygonCount([<layer>])

                Reports the number of polygons in the object, or
                optionally in an individual layer of the Mesh.

            layer(<point>|<polygon>)

                Returns the integer layer number that contains
                the identified point or polygon.

            position([<point>][<polygon>][<layer>])

                This method returns one of two types of values,
                depending upon the argument provided.

                If the argument is a Point identifier, then the
                vector position of the point is returned.

                If the argument is a Polygon identifier, then the
                bounding box of the polygon is returned as a low
                and high vector.

                If the argument is an integer layer identifier,
                then the bounding box of the mesh data found in
                that layer are returned as a low/high vector pair.

                Finally, if no argument is provided, then the
                sum-total bounding box of all mesh data found in
                all valid layers are returned as a low/high vector
                pair.

            vertexCount(<polygon>)

                Reports the integer count of the number of vertices
                that comprise the identified Polygon.

            vertex(<polygon>,<index>)

                Returns the Point identifier found at the indicated
                index offset of the identified Polygon.

            select()

                Cause the object for which the Agent is proxy to
                become the current selection.  In Layout, this selects
                the object in the scene.  In Modeler, this switches the
                object into the active edit.

            select(<point>|<polygon>)           (MODELER ONLY)

                Selects the identified Polygon or Point.  Because
                component selection is unique to mesh editing, this
                method is only available when executing under Modeler.

      New and altered data members for the Mesh Object Agent:

            id                                  (MODELER ONLY)

                This data member holds a string used internally
                by Modeler to uniquely identify the loaded object.

            name                                (MODELER ONLY)

                This data member holds a character string that
                represents the identifier for the object seen by
                the user in the GUI.

            filename                            (MODELER ONLY)

                Holds the full-path filename for the object.  If
                the object has yet to be saved to disc, then this
                value will be 'nil'.
      A new Object Agent type exists globally in LScript, providing an interface to access Vertex
      Maps within the LightWave environment.  The constructor is called VMap(), and in most cases
      can be used globally.

      This constructor returns a VMap Object Agent.  VMaps can be identified by name (string) or
      by integer index of a particular VMap type.  These VMap types are described below.

                    VMSELECT            "select"
                    VMWEIGHT            "weight"
                    VMSUBPATCH          "subpatch"
                    VMTEXTURE           "texture"
                    VMMORPH             "morph"
                    VMSPOT              "spot"
                    VMRGB               "rgb"
                    VMRGBA              "rgba"

      VMap() will return 'nil' if no Vertex Maps of that type currently exist in the system.

      Invoking VMap() without a specific Vertex Map type argument will cause VMap() to return
      the first Vertex Map it encounteres in the system.  From this starting Vertex Map, all
      other existing maps can be iterated through using the next() method.  Iterating through
      maps using next() does not regard type boundaries, so you must watch the type of the
      Vertex Map is you wish to process only specific types.

      The following data members are exported by the Vertex Map Object Agent:

            name

                The name of the Vertex Map associated with the
                Object Agent.

            dimensions

                The number of values associated with each vertex in
                the map.  This value can be zero (0).

            type

                The type of the Vertex Map associated with the Object
                Agent.  It will be one of the constant values defined
                above (VMSELECT, VMWEIGHT, etc.).

      The following methods are exported:

            count()

                Returns the total number of Vertex Maps of the Object
                Agent type found in the current environment.

            isMapped(<point>)

                Reports whether or not the specified point identifier
                exists in the map.  The return value is true or false.

            getValue(<point>[,<index>])

                Returns the value(s) associated with the specified point
                identifier in the Vertex Map.  If a specific value index
                is not provided, the count of items returned will be
                equal to the value contained in the 'dimensions' data
                member.  If the point does not exist in the Vertex Map,
                or the dimensions of the Vertex Map are zero (0), then
                'nil' is returned (use isMapped() and/or the 'dimensions'
                data member to avoid these situations).

            setValue(<point>,<value>|<array>[,<index>])     (MODELER ONLY)

                Sets the value(s) of a specified point identifier in the
                Vertex Map.  If an individual value is provided, then that
                value is placed into the first value slot for the point.
                An optional index value will position the individual
                value into that value slot for the point.

                An array of values can also be provided to be used as
                the values for the point identifier.  If no optional
                index is included, then each element in the array will
                be placed into the corresponding value slot for the
                point identifier in the Vertex Map.  If an index is
                provided, then elements in the array will begin
                populating the values for the point identifer at the
                indicated index offset.

                Modification of a Vertex Map using the setValue()
                method is considered mesh editing, and can only be
                performed within Modeler LScript.  Consequently, this
                method does not exist in Object Agents generated from
                Layout LScripts.  Further, you must be within an
                initiated Mesh Edit session before you can successfully
                invoke this method.

      The following is an example Modeler LScript that allows the user to select an existing Weight
      VMap, and will then apply a scaling factor uniformly to each of its values:

            @version 2.1
            @warnings

            main
            {
                vmap = VMap(VMWEIGHT) || error("No weight maps in mesh!");

                while(vmap && vmap.type == VMWEIGHT)
                {
                    vmapnames += vmap.name;
                    vmap = vmap.next();
                }

                reqbegin("Scale Weight VMap",true);

                c1 = ctlpopup("VMap",1,vmapnames);
                c2 = ctlnumber("Scale by (%)",50.0);

                return if !reqpost();

                vndx = getvalue(c1);
                amount = getvalue(c2) / 100.0;

                reqend();

                vmap = VMap(vmapnames[vndx]) || error("Could not instance VMap '",vmapnames[vndx],"'!");

                selmode(USER);

                moninit(editbegin());

                foreach(p,points)
                {
                    if(vmap.isMapped(p))
                    {
                        values = vmap.getValue(p);

                        for(x = 1;x <= vmap.dimensions;x++)
                            values[x] *= amount;

                        vmap.setValue(p,values);
                    }

                    monstep();
                }

                monend();
                editend();
            }
      A new Object Agent type exists within Layout LScript, providing access to defined channel
      groups.  The ChannelGroup() Object Agent constructor returns a ChannelGroup Object Agent.
      This Object Agent is similar to a standard Layout Object Agent in its interface, but
      only supports a limited subset of the methods and data members.  Its primary purpose is
      to provide a means of enumerating Channel Groups in Layout, some of which may be "hidden"
      (i.e., unassociated with an actual object).

      ChannelGroup() will return 'nil' if no channel groups exist in Layout (which is a highly
      unlikely situation, but you should still code for the possibility in the interests of
      good programming).

      Calling ChannelGroup() without a specific channel group name will return the first channel
      group defined in the system.  From this starting point, all other existing channel groups
      can be iterated through using the next() method.  As with any Layout Object Agent, next()
      will return 'nil' when no further channel groups exist.

      The ChannelGroup() constructor also accepts a channel group identifier (as a string) so
      that you can generate an Object Agent for a specific channel group.  For instance, to
      access the channel group generated by the LW_MasterChannel plug-in:

            group = ChannelGroup("MC");

      Of course, if the LW_MasterChannel plug-in is not active, then this call to ChannelGroup()
      will return 'nil'.

      The following data members are exported by the ChannelGroup Object Agent:

            name

                The name of the channel group for which the Object
                Agent serves as proxy.

            parent

                Holds the parent channel group of the current
                Object Agent.  If no parent exists, this data member
                wil be 'nil'.

      The following methods are exported:

            firstChannel()
            nextChannel()

                Each of these returns a Channel Object Agent (see
                later in this document for new methods and data members
                available from Channel Object Agents).

            next()

                Returns the next channel group in the list.
      LScript's internal IPC mechanism can now be "localized" to the current host (Modeler,
      Screamernet, etc.).  This prevents other processes running the same IPC script on the
      same machine from colliding with established Queues.  This mechanism is enabled with
      the new "@localipc" pragma.

      Please note that this mechanism will not function correctly under Mac operating systems
      up through version 9.  IPC Queues will continue to be global regardless of the presence
      of this pragma in the script.
      Linear arrays can now be appended (or created) using the overloaded '+=' assignment
      operator.  When applied to an existing array, the data being added is placed into
      a new element at the end of the existing linear list of elements:

            a[1] = "Bob";
            a[2] = 1.0;
            a += <1,2,3>;       // placed into a[3]

      In addition, new linear arrays can be generated automatically when the overloaded
      '+=' oeprator is used with a variable containing a 'nil' value:

            vmapnames = nil;
            while(vmap)
            {
                vmapnames += vmap.name;
                vmap = vmap.next();
            }

      Because LScript automatically initializes unused variables with a 'nil' value, the
      explicit assignment of 'nil' illustrated in the code fragment above is only
      necessary if the variable contains a value other than 'nil', or is already
      hosting an existing array.
      The LScript pre-processor supports a new sequential variable designator.  The
      constructor is formed by surrounding an elipse ('..') with integer values that
      serve as the range of values to generate.  The base variable name precedes the
      initial integer value, and is used to generate each declared variable.

      For instance, this sequential veriable designator:

            c1..5;

      will be expanded by the pre-processor to:

            c1,c2,c3,c4,c5;

      Sequential variable designators can be used anywhere in your script code you
      would normally declare multiple variables.  The following code, for example:

            (lex1..5) = parse(" ",line);
            info(lex1..5);

      is functionally equivalent to:

            (lex1,lex2,lex3,lex4,lex5) = parse(" ",line);
            info(lex1,lex2,lex3,lex4,lex5);
      Many new Panels-based requester controls have been added to LScript.

            ctlpercent(<title>,<initial_value>)

                A floating-point mini-slider field that displays values
                as percentages.  Returns a floating-point value with
                getvalue().

            ctlangle(<title>,<initial_value>)

                A floating-point mini-slider field that displays values
                as angles (in degrees).  Returns a floating-point value
                with getvalue().

                Note that angles values are in radians.  Use the LScript
                rad() and deg() functions to convert between degrees and
                radians if you wish to work in degrees.

            ctlchannel(<title>,<width>,<height>[,<selected_channel>])

                A tree-based area that displays all objects in the scene
                that have channels, allowing an individual channel to be
                selected.

                Note that <width> is expressed in pixels, while <height>
                is expressed in number of visible rows.

                Note that <selected_channel> must be a valid Channel
                Object Agent instance.

                Returns a Channel Object Agent instance with getvalue().

            ctlbutton(<label>,<width>,<action_udf>)

                A "do-something-now" button that triggers a pre-defined
                user-defined function within the script.

                Note that <width> is expressed in pixels.

                The following script illustrates the use of the setup
                for, and use of, ctlbutton() control:

                        @version 2.1
                        @warnings

                        c1..2;

                        generic
                        {
                            reqbegin("Testing");

                            c1 = ctlbutton("Increment",150,"addcount");
                            c2 = ctlinteger("Count",1);

                            reqpost();
                        }

                        addcount
                        {
                            setvalue(c2,getvalue(c2) + 1);
                        }

            ctlstate(<label>,<initial_value>,<width>,<action_udf>)

                A boolean button, equivalent to a checkbox.  Like ctlbutton(),
                it triggers a pre-defined user-defined function (<action_udf>)
                within the script.  Unlike ctlbutton(), the UDF takes a value
                that represents the current state of the boolean control.
                A false (0) means it is off, a true (1) means it is on.

                Note that <width> is expressed in pixels.

                The following script illustrates the use of the setup
                for, and use of, ctlstate() control:

                        @version 2.1
                        @warnings

                        generic
                        {
                            reqbegin("State Control");

                            c1 = ctlstate("Testing",true,100,"stateCallback");

                            reqpost();
                        }

                        stateCallback: val
                        {
                            info(val);      // 0 - off, 1 - on
                        }

            ctllistbox(<title>,<width>,<height>,<count_udf>,<name_udf>[,<event_udf>])

                A listbox control that displays a single-column
                collection of text entries.

                Note that <width> is expressed in pixels, while <height>
                is expressed in number of visible rows.

                Two user-defined functions must be defined within the
                script to return the total number of items in the
                listbox (<count_udf>) and the string value at an
                indicated index offset in the listbox (<name_udf>).
                An optional <event_udf> can be defined and specified
                that will receive control whenever an event is triggered
                within the listbox (i.e., an item is selected).

                The <count_udf> accepts no arguments, and returns the
                integer count of items.

                The <name_udf> accepts the integer index value being
                queried, and returns a single string value to be placed
                into that slot.

                The optional <event_udf> accepts the integer index
                value of the selected item, and returns nothing.

                The following script illustrates the setup for, and
                use of, the ctllistbox() control:

                        @version 2.1
                        @warnings

                        c1;

                        lb_items;

                        main
                        {
                            for(x = 1;x <= 5;x++)
                                lb_items += "Item_" + x;

                            reqbegin("Testing List Box");

                            c1 = ctllistbox("Items",300,10,"lb_count","lb_name","lb_event");

                            reqpost();
                        }

                        lb_count
                        {
                            return(lb_items.size());
                        }

                        lb_name: index
                        {
                            return(lb_items[index]);
                        }

                        lb_event: index
                        {
                            info("You selected '",lb_items[index],"'!");
                        }

                In addition, the following script illustrates how
                button controls can be used to manage and interact
                with the listbox contents:

                        @version 2.1
                        @warnings

                        c1..3;

                        lb_items;

                        main
                        {
                            for(x = 1;x <= 5;x++)
                                lb_items += "Item_" + x;

                            reqbegin("Testing List Box");

                            c1 = ctllistbox("Items",300,10,"lb_count","lb_name");
                            c2 = ctlbutton("Add",200,"add_button");
                            c3 = ctlbutton("Delete",200,"del_button");

                            reqpost();
                        }

                        lb_count
                        {
                            // don't use size() here because that
                            // counts all elements, even if they have
                            // 'nil'.

                            return(lb_items.count());
                        }

                        lb_name: index
                        {
                            return(lb_items[index]);
                        }

                        add_button
                        {
                            lb_items += "Item_" + (lb_items.size() + 1);
                            setvalue(c1,lb_items.count());
                        }

                        del_button
                        {
                            sel = getvalue(c1);

                            lb_items[sel] = nil;
                            lb_items.pack();
                            lb_items.trunc();

                            setvalue(c1,lb_items.count());
                        }

                Note that, because of refresh pathways in the Panels
                subsystem, the current selection in the listbox must
                be updated in order to get the listbox to refresh
                on the open panel.  Panels has been optimized to
                avoid refreshes if you set the selection to the
                current selection, so you must select a new value
                each time to trigger the refresh.
      Several new CommandSequence functions have been added to Modeler LScript to support
      Modeler [6].

            close()

                Closes the active object.  If it has been modified,
                then the user will be prompted to save the mesh data
                before it is removed.

            closeall()

                Closes ALL objects in Modeler.  If any objects have
                been modified, then the user will be prompted to save
                each mesh before it is removed.

            exit()

                Terminates Modeler.  Any modified mesh data in the
                system will cause a prompt to be presented to the
                user for saving before Modeler terminates.

            swaphidden()

                Toggles hidden mesh data in the current object
                to become visible, while hiding currently visible
                mesh.

            setlayername([<string>])

                Sets the name for the currently selected foreground
                layer(s).  Calling the function with no arguments
                removes any previously set layer names.

            setobject(<string>[,<index>])

                Selects the active Modeler object.  The optional
                index value specifies which object to select from
                among objects with the same name.

            setpivot(<vector>)

                Sets the pivot point for the current object to the
                specific location.

            unweld()

                Disconnects the shared vertices of two or more
                polygons so that each has a complete set of unshared
                points.

            weldaverage()
      The smoothshift() command now has a third optional argument that specifies the scaling
      factor to be applied as a floating-point value.

            smoothshift(<distance>[,<maxangle>[,<scale>]])
      A new time() function is available now that will return, as multiple items, the hour,
      minute, second, and tick values for the current time.  If a time value is provided as
      an argument (defined as the number of seconds that have elapsed since January 1st, 1900),
      then returns values are relative to that time index.

      The hour is in 24-hour military form, so values range from 0 to 23.  The minutes and
      seconds range from 0 to 59.

      The 'tick' value is the number of seconds that that have elapsed since January 1st, 1900.

            ...
            (h,m,s,t) = time();
            ...
      A new date() function is available now that will return, as multiple items, the day of
      the month, month, 4-digit year, day of the week, and the day of the year (a.k.a. julian
      value), string value for the current month, and string value for the current day of the
      week.  If a time index value is provided as an argument (defined as the number of seconds
      that have elapsed since January 1st, 1900), then the date() return values are relative to
      that time index.

      The day of the week begins at Sunday==1, and the day of the year runs from 1 to 365.

            ...
            (d,m,y,w,j,sm,sw) = date();
            ...
      The selpolygon() Modeler LScript function now accepts a PART selection condition, which
      is parametered by the string part name to be selected.

            ...
            selmode(USER);
            selpolygon(SET,PART,"LeftButtock");
            ...
      The following Layout Command Sequence functions have been added to LScript (Generic and
      Master), synchronizing LScript with Layout build 474:

            PreviousSibling()
            NextSibling()
            CenterItem()
            ShowSafeAreas()
            ShowFieldChart()
            CacheRadiosity()
            CacheCaustics()
            CacheShadowMap()
            EditPlugins()
            FitAll()
            FitSelected()
            EnableVIPER()
            RayTraceShadows()
            RayTraceReflection()
            RayTraceRefraction()
            FogType()
            FogMinDistance()
            FogMaxDistance()
            FogMinAmount()
            FogMaxAmount()
            LightIntensityTool()
            TopView()
            BottomView()
            BackView()
            FrontView()
            RightView()
            LeftView()
            SchematicView()
            EnableVolumetricLights()
            AddPartigon()
            EnhancedAA()
            PolygonEdgeColor(<red>,<green>,<blue>|<red,green,blue>)
            MaskPosition(<left>,<top>,<width>,<height>)
            MaskColor(<red>,<green>,<blue>|<red,green,blue>)
            IncludeLight(<id>|<index>)
            ExcludeLight(<id>|<index>)
            Antialiasing([1-9])
            AddEnvelope(<channel>)
            RemoveEnvelope(<channel>)
            FogColor(<red>,<green>,<blue>|<red,green,blue>)
            AutoConfirm(true|false)
            MorphTarget(<name>|<type>,<index>|<id>)
            SaveSceneCopy(<filename>)
            SchematicPosition(<x>,<y>)
            RadiosityTolerance(<number>)
            SaveObject(<filename>)
            SaveObjectCopy(<filename>)
            SaveTransformed(<filename>)

      Color values should be specified as floating-point values.

      Calling Antialiasing() without arguments turns antialiasing off for the selected Camera.
      The values 1-9 correspond to the antialiasing values available as of Layout build 459.

      NOTE: be aware that running LScript v2.1 with builds of LightWave Layout prior
      to 474 may generate error messages concerning unknown Command Sequence functions.
      A filestat() function has been added to return various system-related values associated
      with the file on disc.  When passed a valid filename, filestat() returns (in order)
      the file's last access time, creation time, and last modification time, the file size,
      the number of links to the file (useful only under UNIX or NTFS file systems), the
      file owner's user id (useful only under UNIX), and the file's group id (also useful
      only under UNIX).

            ...
            (a,c,m,s,l,u,g) = filestat("/etc/profile");
            ...

      All values are integer, and the access, creation, and modification times are suitable
      for use as arguments to the time() and date() functions.
      The addcurve() function now optionally accepts a third parameter to indicate the state
      of the curve.  You can now provide START or END flags to indicate the control structure
      of the curve.  If no parameter is provided, then no controls will be applied to the curve.
      Two new functions have been added to the LScript toolset for gathering information about
      the host application's version.

            hostVersion()

                returns the host application's version number
                as a floating-point value.  It contains the
                major and minor values of the version.  For
                example, 6.1.

            hostBuild()

                returns the sub-version build value of the
                application as an integer.  Because LightWave
                build numbers are linear and are never reset,
                this value is a more accurate means of
                determining application feature sets than the
                version number.
      The pre-processor's @if/@end conditional code system has two new values that can be used as
      conditions.  The 'host_version' constant represents the application's current version as a
      floating-point value, and the 'host_build' contains the host application's current build
      number as an integer.

            ...
            @if host_build > 410
            ...
            @end
            ...
      Two new Requester commands have been added for Layout.  These new commands enable non-modal
      Requester panels in LScript.

      reqopen() can be used in place of reqpost() to open the Requester panel in non-modal
      mode.  In this mode, the panel remains open and interactive to the user even after the
      options() UDF has terminated.  In order to process changes to panel controls in non-modal
      mode, controls must have an active refresh callback applied.  As controls are modified,
      these refresh callbacks are invoked by LScript, and the script's process() UDF is
      subsequently invoked by Layout to refresh the object's onscreen attributes.

      reqisopen() is used to determine whether or not a Requester panel opened non-modally
      is currently open.  A Boolean true is returned if the panel is currently open.  A non-modal
      panel can be closed by calling reqend().

            ...
            if(reqisopen())
                reqend();
            else
            {
                reqbegin(myObj.name);

                c1 = ctlangle("Heading",rad(myHeading));
                c2 = ctlangle("Pitch",rad(myPitch));
                c3 = ctlangle("Bank",rad(myBank));

                ctlrefresh(c1,"heading_refresh");
                ctlrefresh(c2,"pitch_refresh");
                ctlrefresh(c3,"bank_refresh");

                reqopen();
            }
            ...

      Non-modal Requester panels are only available to Layout LScripts, with Generic LScripts
      excluded.
      A plethora of new data members and methods have been added to the Channel Object Agent,
      bringing it to maturity.

      The following data members have been added:

            keyCount

                Returns the number of keys contained in the envelope
                associated with the channel.

            keys[]

                A linear array that holds the keyframe identifiers
                for all the keys contained in the envelope.  It will
                always be 'keyCount' in length.

            preBehavior

                Holds the type of the pre-behavior associated with
                the envelope.  It will be one of CHAN_RESET,
                CHAN_CONSTANT, CHAN_REPEAT, CHAN_OSCILLATE,
                CHAN_OFFSET, CHAN_LINEAR.  The string values
                "reset", "constant", "repeat", "oscillate",
                "offset", or "linear" can also be used.

                Unlike most, this data member is NOT read-only.
                Assigning one of the above constants to it will
                actually alter the pre-behavior of the associated
                envelope.

            postBehavior

                Holds the type of the post-behavior associated with
                the envelope.  With the exception of it's name, this
                data member functions identically to the 'preBehavior'
                data member.  It returns or alters the post-behavioral
                setting for the envelope.

      The following methods have been added:

            keyExists(<time>)

                Returns either 'nil' if no key exists at the specified
                time index, or the identifier of the keyframe if it
                does.

            setKeyValue(<key>,<value>)

                Sets the value of the provided keyframe to the provided
                value.  <value>'s are always floating-point numbers.

            setKeyTime(<key>,<time>)

                Alters the time index for the specified keyframe.

            setKeyCurve(<key>,<shape>)

                Sets the type of interpolation that is used to evaluate
                the keyframe.  This can be one of CHAN_TCB, CHAN_HERMITE,
                CHAN_BEZIER, CHAN_LINEAR, CHAN_STEPPED.  In addition, the
                strings "TCB", "Hermite", "Bezier", "Linear", and "Stepped"
                are also acceptable.

            setKeyHermite(<key>,<parm1>,<parm2>,<parm3>,<parm4>)

                This method allows you to set the four Hermite Spline
                coefficients.  If you don't understand their use,
                you probably won't be using this method anyway.

            setKeyTension(<key>,<value>)

                Sets the tension value for the specified keyframe.

            setKeyContinuity(<key>,<value>)

                Sets the continuity value for the specified keyframe.

            setKeyBias(<key>,<value>)

                Sets the bias value for the specified keyframe.

            getKeyValue(<key>)

                Returns the floating-point value associated with the
                specified keyframe.

            getKeyTime(<key>)

                Returns the time index for the specified keyframe.

            getKeyCurve(<key>)

                Returns the curve type for the specified keyframe.
                See the entry for setKeyCurve() for a list of the
                constant values that are returned.

            getKeyHermite(<key>)

                Returns the four Hermite Spline coefficients that
                are currently in use for the specified keyframe.
                If getKeyCurve() does not return CHAN_HERMITE, then
                you probably don't want to call this method.

            getKeyTension(<key>)

                Returns the current tension setting for the keyframe.

            getKeyContinuity(<key>)

                Returns the current continuity setting for the keyframe.

            getKeyBias(<key>)

                Returns the current bias setting for the keyframe.

            createKey(<time>,<value>)

                This method creates a new keyframe at the specified
                time index with the provided initial value.  If the
                key is created successfully, the keyframe identifier
                is returned, otherwise 'nil' is returned.  This method
                will cause the 'keyCount' and 'keys[]' data members
                to instantly update.

            deleteKey(<key>)

                Keyframes can be deleted from the channel's envelope
                with this method.  Because this method also causes
                the 'keyCount' and 'keys[]' data members to update
                in real time, you need to be very careful when using
                it within loops that are based no these data members.

      Keyframe identifiers are not directly usable by your script, and are returned to you as
      integer data types.  Their only purpose is for use as arguments to methods that require
      them.
      A new Envelope Object Agent has been added to LScript.  The Envelope() constructor takes
      three arguments, the last of which is optional:

            Envelope(<name>,<type>[,<group>])

                The <name> argument is a string identifier for the new
                envelope.  The <type> value is one of CHAN_NUMBER,
                CHAN_DISTANCE, CHAN_PERCENT, or CHAN_ANGLE.

                The optional <group> argument is a string value that
                identifies the group within which the new envelope will
                reside.  If the <group> indicated does not already exist,
                LScript will create it.

      This Object Agent shares the same methods and data members as those just added to the
      Channel Object Agent (see above).  In addition, it provides the following
      Envelope-specific methods:

            copy(<Envelope>)

                This method will copy into the instance the values found
                in the provided Envelope Object Agent.

            edit()

                The contents and settings of the Envelope will be displayed
                to the user for their interaction.

            save()
            load()

                These two methods are used to stream the Envelope's settings
                and data into and out of the scene file.  They are context-
                sensitive, meaning that they can only be called during those
                particular phases of Layout--i.e., the save() method can only
                be invoked from the save() UDF, and the load() method can only
                be invoked from within the load() UDF.  Hellfire and damnation
                shall be your earthly reward should you attempt to use them
                outside of their respective context.

            persist([<Boolean>])

                Envelopes (and groups) generated by your script are
                automatically destroy by LScript when your script terminates.
                You can cause them to remain in Layout's internal channel
                group listing by calling this method.  If called without
                arguments, the Envelope will be flagged as persistent.  A
                Boolean 'false' value will allow the Envelope to be removed
                by LScript at script termination.
      A new format() function can be used to format a string by performing
      token substitution within the string with provided values.  This is
      much like C's printf() function.  It can be used with individual
      data elements, but is intended more for use with arrays.

            format(<template>,<data>[,<data>...])

      Tokens are indicated by a dollar sign ($) followed by the numeric
      index value to be taken from the arguments provided.  Index value
      need not be sequential, nor must you access all elements.

      Individual arguments can be either a list of single data types
      (variables or constants), or you can provide a single array value
      to the used in the substitution.  YOU CANNOT PROVIDE BOTH.

      The function returns a string value that has all substitutions
      performed:

            ...
            d = date();
            info(format("Today is $7, $6 $1, $3",d));

            (h,m) = time();

            ap = "AM";
            if(h > 11)
            {
                ap = "PM";
                h -= 12 if h > 12;
            }
            h = 12 if h == 0;

            info(format("Time is currently $1:$2" + ap,h,m));
            ...

## Behavioral Changes

      The following legacy requester control creation functions have been removed from LScript:

            addcontrol()
            addtext()
            addhsv()
            addrgb()
            additems()
            addcheckbox()
            addfilename()
      The following legacy Modeler layer management functions have been removed from LScript:

            fglayers()
            bglayers()
            getempty()
            getfull()
            getemptyfg()
            getemptybg()
            swaplayers()
            setlayer()
            setblayer()
      In LightWave [6], the Panels interface mechanism is no longer implemented as a plug-in.
      Rather, it has become a subsystem of LightWave itself.  Because of this, there is no
      longer a possibility that Panels will not be available to any LScript.  For this reason,
      the reqbegin() function no longer returns any value indicating the presence or absence
      of the Panels plug-in.
      In an effort to migrate LScripts to the more-robust Panels interface mechanism, the
      reqbegin() function in Modeler LScript now inversely interprets the optional boolean
      value that can be provided to it.  In past releases of LScript, if the optional boolean
      value provided indicated 'true', then the Panels interface system was used (if available)
      to construct the script's requester panel.  A value of 'false' (or no value at all)
      caused Modeler's internal panel creation mechanism to be employed.

      This has now been completely reversed.  Now, the Panels subsystem is selected by default,
      and you must indicate a boolean 'true' value to select Modeler's internal requester
      system.  THIS MEANS THAT ALL MODELER SCRIPTS THAT POST REQUESTERS MUST BE CHANGED if
      they are going to be run under LScript v2.1.
      The Displacement Map support for the "BEFOREBONES" flag() value has been renamed to
      "PREBONE".
      The Channel Object Agent constants introduced in LScript v2.0 have all been renamed to
      include a "CHAN_" prefix.

## Bug Fixes

      Corrected argument handling in Object Agent methods to account for multiple-element
      arguments (such as Init Blocks).
      The type of the Object Agent provided to the Item Animation create() function was hard-coded
      as type MESH.  I must have forgotten my Ginkgo biloba that day.
      Recursive plug-in invocations (such as occur when you ask an Item Animation script to return
      the world position of the object to which it is attached) were trashing their own instance
      data because the internal switching stack was not cognizant of situations where the instance
      data was already installed globally.
      Passing a 'nil' value as an array index would crash the application.  This has been trapped.
      The lyrsetfg(), lyrsetbg(), lyrswap() and boundingbox() functions were not compliant with
      Modeler's new unlimited layer mechanism, leaving them limited to only reaching layer numbers
      32 or less (the number of bits in an integer).
      The File Object Agent write() method, while already coded to work with linear arrays as
      arguments, was performing processing intended for non-binary-mode files when the files were
      in binary mode, leading, in some cases, to memory overruns and crashes.
      Corrected the following Layout Command Sequence functions to accept a vector argument as
      well as three floating-point values:

            Position()
            Rotation()
            Scale()
            PivotPosition()
            PivotRotation()
      The Image Object Agent rgb() method was using bad index values when returning the RGB data.
      The Image Object Agent wasn't being treated correctly by the Requester subsystem, making
      access to its data/members impossible.
      The foreach() iterator was not initializing its variable container, letting it start at
      whatever value it might already have been holding.
      Object Agent data member validation was broken, allowing references to invalid data members
      (i.e., "shadow" instead of "shadows") to go quietly unpunished.
      The Mac LScript random() function was not working correctly, returning the same number
      each time it was called.  This was likely due to Apple's philosophy of disallowing
      uniqueness.
      Requester separator controls were not being properly initialized when they were assigned to
      specific Tab control pages using ctlpage().
      Function argument variables could not be assigned values within the UDF to override their
      passed or default values.
      The LScript debugger interface code contained a bug that would not correctly identify a
      sub-UDF variable when an attempt was made in the LSIDE Debugger to add a watch for it.
      CommandSequence calls could not be made from the Master options() UDF without causing a
      crash.
      Index values used to declare arrays could be less than 1, leading to weird errors or
      crashes.
      The mathematical assignment operators +=, -=, *=, and /= would generate an error message
      when an integer appeared on the left, and a floating point value on the right.