LScript v2.1 Release Notes

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.