[Max .Net] Getting Sub anim/Animatable values.

Become a member of the CGSociety

Connect, Share, and Learn with our Large Growing CG Art Community. It's Free!

THREAD CLOSED
 
Thread Tools Search this Thread Display Modes
Old 12 December 2013   #1
[Max .Net] Getting Sub anim/Animatable values.

Hi.

I've been struggling with this one for a while.

I essentially just want to traverse a node's animatable/sub anim tree, in .net, and get the current values.

So, as an example of what i'm attempting, here is a mock-up of how I would do it in mxs


fn recurseSubAnims theSubAnim levelStr:"--" =(
	
	-- loop to the number of sub animation tracks (.numsubs)
	for subIndx = 1 to theSubAnim.numSubs do(
		
		-- get a handle to the child sub anim at the index number.
		local childSubAnim = getSubAnim theSubAnim subIndx
		
		-- we'll just do some nice printing for now
		print (levelStr + "> " + childSubAnim.name)
		
		-- here's where i get stuck in the api
		if childSubAnim.Controller != undefined then(
			-- I WANT THIS VALUE --
			print (levelStr + "> VALUE >> " + (childSubAnim.Controller.value as string))
		)
		
		-- call this function again, using childSubAnim now as the element to search for further child subanims
		recurseSubAnims childSubAnim levelStr:(levelStr+"--")
	)
	
)

-- kick off the recursion at the top level with a single selected node
recurseSubAnims (Sphere())


So below is some extracted code on how I am attempting it in .net (c#). It's a method that is trying to populate a tree view with sub anim data (so ignore the treeviewitem stuff).

 

public void populateTree( IINode node ){

            int sceneTime = Kernel.Interface.Time;
            
            // get a list of all selected nodes.
            List<IINode> selectedNodes = MaxUtils.getSelectedNodes();
            
            // loop the selected nodes
            for (int i = 0; i < selectedNodes.Count; i++)
            {
                // variable to current Inode
                IINode maxNode = selectedNodes[i];

                // create a tree item
                TreeViewItem nodeTreeItem = new TreeViewItem();
                nodeTreeItem.Header = maxNode.Name;
                this.treeView_subAnims.Items.Add(nodeTreeItem);
				
				//**************************************************  ********************************
				// below i am only trying to get the position x value of a selected node...
				
                // get the matrix controller
                IControl tm = node.TMController;
				
				// get the position controller instance.. 
				// c# uses properties to replace pointers
                IControl pc = tm.PositionController;
                
				// create an interval
				IInterval ivalid = Kernel.Global.Interval.Create();
				
				// set it to now
                try
                {
                    ivalid.SetInstant(sceneTime);
                }
                catch { }
				
				// create a system object to hold the unkown value type
                object pXval = new object();

                try
                {
                    //allways crashes max, with no debug info on what caused it..
                    pc.XController.GetValue(sceneTime, ref pXval, ivalid, GetSetMethod.Relative);
                    
                }
                catch { }

            }
        }


So, my problem is, whenever i call the GetValue method it instantly crashes max, with no help or data (that I can see anyways). Using a try{} doesn't help avoid the crash.

Looking at the c++ docs isn't revealing any clues for me.

Above I'm using the position x controller as a test, but i would eventually like to get values from the entire subAnim tree.

There's an awesome chance that I may be going about this completely wrong, hence seeking help.

Just wondered if anyone had actually used this method or attempted to get similar data.

Andy
 
Old 12 December 2013   #2
my guess would be that pc is invalid or what ever they are called in c# in c++ it would be a null pointer.

from sdk ref

virtual Control* Control::GetPositionController()
  
  Default Implementation: {return NULL;}

Last edited by Klunk : 12 December 2013 at 02:20 PM.
 
Old 12 December 2013   #3
also I think your approach is perhaps not the best, here's a mxs extension function that shows a better method of enumerating a control subanims.

prints the subanim hierarchy to the listener
def_visible_primitive(EnumController, "EnumController");
       
       void EnumControl(Control* cntrl)
       {
       // do what ever here, we just print to the listener
       
       	MSTR name;
       	cntrl->GetClassName(name);
       	the_listener->edit_stream->printf("%s\n", name);
       
       	int numsubs = cntrl->NumSubs();
       	for(int i = 0; i < numsubs; ++i)
       	{
       		Animatable* anim = cntrl->SubAnim(i);
       		if(!anim)
       			continue;
       		Control* subcntrl = (Control*)anim->GetInterface(I_CONTROL); // do we have a control (theres no single superclass id for controls)
       		if(subcntrl)
       			EnumControl(subcntrl);
       	}
       }
       
       
       Value* EnumController_cf(Value **arg_list, int count)
       {
       	check_arg_count(EnumController, 1, count);
       	Control* cntrl = arg_list[0]->to_controller();
       	EnumControl(cntrl);
       	return &ok;
       }

you'll have to work out how that's ported to c# as it's not my bag !
output would then be
EnumController $.transform.controller
     Position/Rotation/Scale
     Position XYZ
     Bezier Float
     Bezier Float
     Bezier Float
     Euler XYZ
     Bezier Float
     Bezier Float
     Bezier Float
     Bezier Scale

Last edited by Klunk : 12 December 2013 at 05:57 PM.
 
Old 12 December 2013   #4
Yea i tried casting to a Control (IControl in c#) , but it still crashes.

I ported the print code you posted. I got it printing out as should.. but when i attempt the getvalue (commented out) method it crashes.

    
class CtrlTest{

        IGlobal _global = null;
        IInterface _interface = null;

        public CtrlTest()
        {
            this._global = Autodesk.Max.GlobalInterface.Instance;
            this._interface = this._global.COREInterface13;
        }


        public void PrintToListener(string strMsg)
        {
            this._global.TheListener.EditStream.Printf(strMsg + "\n", null);
            return;
        }
 


// -----------------------------------------------------------------------------------------       
// Crappy functions to get selection... will re-work
        private void getChildNodesRecurse(IINode node, List<IINode> array)
        {

            array.Add(node);
            for (int i = 0; i < node.NumChildren; i++)
            {
                getChildNodesRecurse(node.GetChildNode(i), array);
            }
        }

        public List<IINode> getSceneNodesList()
        {
            // make a list to fill

            List<IINode> nodeList = new List<IINode>();
            // loop from the scene 'Root' node
            for (int i = 0; i < this._interface.RootNode.NumChildren; i++)
            {
                // call recusrive function and populate list with all nodes
                this.getChildNodesRecurse(this._interface.RootNode  .GetChildNode(i), nodeList);
            }

            return nodeList;
        }

        public List<IINode> GetSelectedNodes()
        {
            // make a list to fill
            List<IINode> selectionList = new List<IINode>();
            // for all the nodes in the scene
            foreach (IINode node in (this.getSceneNodesList()))
            {
                // if it's selected
                if (node.Selected == true)
                {
                    // add it to the return list
                    selectionList.Add(node);
                }
            }
            return selectionList;
        }
// Crappy functions to get selection... will re-work
// -----------------------------------------------------------------------------------------       



        public void EnumControl(IControl cntrl)
        {
            // do what ever here, we just print to the listener

            string name = cntrl.ClassName;
            this.PrintToListener(name);

            int numsubs = cntrl.NumSubs;
            for (int i = 0; i < numsubs; ++i)
            {
                IAnimatable anim = cntrl.SubAnim(i);
                if (anim == null)
                    continue;
                
                IControl subcntrl = (IControl)anim.GetInterface(InterfaceID.Control); // do we have a control (theres no single superclass id for controls)

                if (subcntrl != null)
                {

                    /* Uncomment to attempt value get
                    //-----------------------------------------------------------------
                    // Added stuff to get the value
                    object val = null;

                    IInterval interval = _global.Interval.Create();
                    interval.SetInfinite();

                    // crashy crash crash.                 
                    cntrl.GetValue(this._interface.Time, ref val, interval, GetSetMethod.Relative);

                    if (val != null)
                    {
                        this.PrintToListener("--> " + val.ToString());
                    }
                    // Added stuff to get the value
                    //-----------------------------------------------------------------
                    */

                    this.EnumControl(subcntrl);
                }
            }
        }



// -----------------------------------------------------------------------------------------       
// kick off from some selected node.
        public void PrintControllerTree()
        {
            List<IINode> sel = this.GetSelectedNodes();
            if (sel.Count > 0)
            {
                // just grab the first node
                IINode node = sel[0];

                // same as the EnumControl.. just so we get the sub anim as a control..
                int numsubs = node.NumSubs;
                for (int i = 0; i < numsubs; ++i)
                {
                    IAnimatable anim = node.SubAnim(i);
                    if (anim == null)
                        continue;
                    IControl subcntrl = (IControl)anim.GetInterface(InterfaceID.Control); // do we have a control (theres no single superclass id for controls)
                    if (subcntrl != null)
                        // enter the recursion
                        this.EnumControl(subcntrl);
                }

                //EnumControl(IControl cntrl)
            }

        }

    }


Its possible it's the 'void*' <> 'ref object val' conversion. Unfortunately the function signature requires an 'object' type and throws an error if i pass anything else. Not sure if there's a way of tricking it using anther type... i'm no c# guru either.

This is frustrating.

Thanks for looking though.
 
Old 12 December 2013   #5
you will probably need to test the classid of the control to see if to pass
float, Point3, Point4, AColor etc

the use the following if it's a point3 controller

Point3 pnt;
     cntrl->GetValue  (  t,&pnt, FOREVER);
    


also the GetValue call should be up with the print to listener.

you could try
float val[12]; // forgot matrix3
 cntrl->GetValue  (  t,val, FOREVER);

as a coverall

Last edited by Klunk : 12 December 2013 at 07:26 PM.
 
Old 12 December 2013   #6
a more correct method would be

void EnumControl(Control* cntrl)
 {
 // do what ever here
 
 	MSTR name;
 	cntrl->GetClassName(name);
 	the_listener->edit_stream->printf("%s\n", name);
 	TimeValue t = MAXScript_interface->GetTime();
 
 	switch(cntrl->SuperClassID())
 	{
 		case CTRL_FLOAT_CLASS_ID:
 		{
 			float val;
 			cntrl->GetValue(t, &val, FOREVER);
 			the_listener->edit_stream->printf("Float %f\n", val);
 			break;
 		}
 		case CTRL_POINT3_CLASS_ID:
 		case CTRL_POSITION_CLASS_ID:
 		{
 			Point3 val;
 			cntrl->GetValue(t, &val, FOREVER);
 			the_listener->edit_stream->printf("Point3 [%f,%f,%f]\n", val.x,val.y,val.z);
 			break;
 		}
 		case CTRL_MATRIX3_CLASS_ID:
 		{
 			Matrix3 tm;
 			cntrl->GetValue(t, &tm, FOREVER);
 			the_listener->edit_stream->printf("Matrix3 [%f,%f,%f] [%f,%f,%f] [%f,%f,%f] [%f,%f,%f]\n", tm[0].x,tm[0].y,tm[0].z, 
 					tm[1].x,tm[1].y,tm[1].z, tm[2].x,tm[2].y,tm[2].z, tm[3].x,tm[3].y,tm[3].z);
 			break;
 		}
 		case CTRL_ROTATION_CLASS_ID:
 		{
 			Quat val;
 			cntrl->GetValue(t, &val, FOREVER);
 			the_listener->edit_stream->printf("Quat [%f,%f,%f,%f]\n", val.x,val.y,val.z ,val.w);
 			break;
 		}
 		case CTRL_SCALE_CLASS_ID:
 		{
 			ScaleValue val;
 			cntrl->GetValue(t, &val, FOREVER);
 			the_listener->edit_stream->printf("ScaleValue [%f,%f,%f] [%f,%f,%f,%f]\n", val.s.x,val.s.y,val.s.z ,val.q.x,val.q.y,val.q.z ,val.q.w);
 			break;
 		}
 	}
 
 	int numsubs = cntrl->NumSubs();
 	for(int i = 0; i < numsubs; ++i)
 	{
 		Animatable* anim = cntrl->SubAnim(i);
 		if(!anim)
 			continue;
 		Control* subcntrl = (Control*)anim->GetInterface(I_CONTROL); // do we have a control (theres no single superclass id for controls)
 		if(subcntrl)
 			EnumControl(subcntrl);
 	}
 }

Last edited by Klunk : 12 December 2013 at 10:10 AM.
 
Old 12 December 2013   #7
Klunk, you are a legend.

So it seems in c# that i needed to create an 'object' type variable and then initialize or cast the type needed to that object variable.

So something like this worked and it's now giving me good values and not crashing...


// make an 'object' type to satisfy the parameter
object val = null;

// give it a float (or whatever) memory allocation
val = 0.0f; 

// call the function with the allocated val as reference
cntrl.GetValue(this._interface.Time, ref val, interval, GetSetMethod.Relative);


So i fleshed out the EnumControl method with your example.

       

        public void EnumControl(IControl cntrl)
        {
            
            // print the name of this control
            this.PrintToListener(cntrl.ClassName);

            // make an object type to be passed to the getvalue
            object val = null;

            // make an interval class and set it to forever
            IInterval interval = _global.Interval.Create();
            interval.SetInfinite();

            // A switch/case to test the super class id of the control
            switch (cntrl.SuperClassID)
            {
                case SClass_ID.CtrlInteger:
                    // make a local var of the type this super class requires
                    int _val_Int = 0;

                    // populate var with the type we want as an object.. GetVal rewuires an object.. but initialized to the correct memory type                            
                    val = _val_Int as object;

                    // populates val with the value.                                                             
                    cntrl.GetValue(this._interface.Time, ref val, interval, GetSetMethod.Absolute);

                    // now convert val back to our typed var, so we can use it (var is still an object so wont work as point3 or matrix...ect)
                    _val_Int = (int)val;

                    this.PrintToListener("--> " + _val_Int.ToString());
                    break;
                case SClass_ID.CtrlFloat:
                    // make a local var of the type this super class requires
                    float _val_Flt = 0.0f;

                    // populate var with the type we want as an object.. GetVal rewuires an object.. but initialized to the correct memory type                                                        
                    val = _val_Flt as object;

                    // populates val with the value.                   
                    cntrl.GetValue(this._interface.Time, ref val, interval, GetSetMethod.Absolute);

                    // now convert val back to our typed var, so we can use it (var is still an object so wont work as point3 or matrix...ect)
                    _val_Flt = (float)val;

                    this.PrintToListener("--> " + _val_Flt.ToString());

                    break;
                case SClass_ID.CtrlPoint3:
                    // make a local var of the type this super class requires
                    IPoint3 _val_p3 = this._global.Point3.Create(0.0, 0.0, 0.0);

                    // populate var with the type we want as an object.. GetVal rewuires an object.. but initialized to the correct memory type                                                                                    
                    val = _val_p3 as object;

                    // populates val with the value.                   
                    cntrl.GetValue(this._interface.Time, ref val, interval, GetSetMethod.Absolute);

                    // now convert val back to our typed var, so we can use it (var is still an object so wont work as point3 or matrix...ect)
                    _val_p3 = (IPoint3)val;

                    this.PrintToListener("--> " + _val_p3.ToString());

                    break;
                case SClass_ID.CtrlPosition:

                    // make a local var of the type this super class requires
                    IMatrix3 _val_tm = this._global.Matrix3.Create();

                    // populate var with the type we want as an object.. GetVal rewuires an object.. but initialized to the correct memory type
                    val = _val_tm as object;

                    // populates val with the value.                 
                    cntrl.GetValue(this._interface.Time, ref val, interval, GetSetMethod.Absolute);

                    // now convert val back to our typed var, so we can use it (var is still an object so wont work as point3 or matrix...ect)
                    _val_tm = (IMatrix3)val;

                    // print
                    this.PrintToListener("--> " + _val_tm.Trans.X.ToString() + ", " + _val_tm.Trans.Y.ToString() + ", " + _val_tm.Trans.Z.ToString());

                    break;

                case SClass_ID.CtrlMatrix3:
                    // make a local var of the type this super class requires
                    _val_tm = this._global.Matrix3.Create();

                    // populate var with the type we want as an object.. GetVal rewuires an object.. but initialized to the correct memory type
                    val = _val_tm as object;

                    // populates val with the value.                 
                    cntrl.GetValue(this._interface.Time, ref val, interval, GetSetMethod.Absolute);

                    // now convert val back to our typed var, so we can use it (var is still an object so wont work as point3 or matrix...ect)
                    _val_tm = (IMatrix3)val;

                    // print
                    this.PrintToListener("--> " + _val_tm.Trans.X.ToString() + ", " + _val_tm.Trans.Y.ToString() + ", " + _val_tm.Trans.Z.ToString());

                    break;
                case SClass_ID.CtrlQuat:
                    // make a local var of the type this super class requires
                    IQuat _val_q = this._global.Quat.Create();

                    // populate var with the type we want as an object.. GetVal rewuires an object.. but initialized to the correct memory type
                    val = _val_q as object;

                    // populates val with the value.                 
                    cntrl.GetValue(this._interface.Time, ref val, interval, GetSetMethod.Absolute);

                    // now convert val back to our typed var, so we can use it (var is still an object so wont work as point3 or matrix...ect)
                    _val_q = (IQuat)val;

                    IntPtr x = new IntPtr(), y = new IntPtr(), z = new IntPtr();
                    _val_q.GetEuler(x, y, z);

                    

                    // now convert val back to our typed var, so we can use it (var is still an object so wont work as point3 or matrix...ect)
                    this.PrintToListener("--> [" + _val_q.Vector.X.ToString() + ", " + _val_q.Vector.Y.ToString() + ", " + _val_q.Vector.Z.ToString() + "]");

                    break;
                case SClass_ID.CtrlRotation:
                    // make a local var of the type this super class requires
                    _val_q = this._global.Quat.Create();

                    // populate var with the type we want as an object.. GetVal rewuires an object.. but initialized to the correct memory type
                    val = _val_q as object;

                    // populates val with the value.                 
                    cntrl.GetValue(this._interface.Time, ref val, interval, GetSetMethod.Absolute);

                    // now convert val back to our typed var, so we can use it (var is still an object so wont work as point3 or matrix...ect)
                    _val_q = (IQuat)val;

                    // now convert val back to our typed var, so we can use it (var is still an object so wont work as point3 or matrix...ect)
                    this.PrintToListener("--> [" + _val_q.Vector.X.ToString() + ", " + _val_q.Vector.Y.ToString() + ", " + _val_q.Vector.Z.ToString() + "]");

                    break;
                case SClass_ID.CtrlScale:
                    // make a local var of the type this super class requires
                    IScaleValue _val_sv = this._global.ScaleValue.Create();

                    // populate var with the type we want as an object.. GetVal rewuires an object.. but initialized to the correct memory type
                    val = _val_sv as object;

                    // populates val with the value.                 
                    cntrl.GetValue(this._interface.Time, ref val, interval, GetSetMethod.Absolute);

                    // now convert val back to our typed var, so we can use it (var is still an object so wont work as point3 or matrix...ect)
                    _val_sv = (IScaleValue)val;

                    // now convert val back to our typed var, so we can use it (var is still an object so wont work as point3 or matrix...ect)
                    this.PrintToListener("--> " + _val_sv.S.ToString());

                    break;
                default:
                    //default stuff
                    break;
            }

            // now loop any child sub anims, convert to control and recurse
            int numsubs = cntrl.NumSubs;
            for (int i = 0; i < numsubs; ++i)
            {
                IAnimatable anim = cntrl.SubAnim(i);
                if (anim == null)
                    continue;
                
                IControl subcntrl = (IControl)anim.GetInterface(InterfaceID.Control); // do we have a control (theres no single superclass id for controls)

                if (subcntrl != null)
                {

                    this.EnumControl(subcntrl);
                }
            }
        }


.. praise Odin!

The above method is still pretty rough, i'm going to clean it refactor some of it and clean it up (too much copied code), but it works.

One thing to note, I had to create local variables inside the switch, of the type needed, as c# wouldn't expose the methods on the 'object val' variable even if i re-cast it; so '(IPoint3)val' would not expose the .x, .y, .z... and so on, after i got the value from GetValue. So I had to re-assign val as #new_type# to use it. Maybe there's a cleaner c# way to handle this, i'm not sure.

Anyway, many thanks for the help. Nice to put this one to bed.
 
Old 12 December 2013   #8
I have to ask, what are the advantages of using c# over c++ for stuff like this ? You are using exactly the same code just with a slightly different syntax and a bit of added complexity. Do you still need Visual Studio ? Is there better compatibility across different versions of max like mxs ?

Last edited by Klunk : 12 December 2013 at 08:14 AM.
 
Old 12 December 2013   #9
Possibly my own laziness and lack of patience. It's a valid question. I'm actually faced with that right now.

I'd say:

1. Ease of setup.

I have tried several times to jump into the c++ side of things (last week in fact). I'd think "right i'm going to learn this... just dive in and smash through it". Then I'd hit hurdles with it before I even start coding. Some project setting or compiler issue.. an absolute nightmare to debug, and a huge frustration.

C# is SUPER simple to setup. Just reference the dll's you want and go. No lib/include/linker headaches.

2. Nicer language.

C# is cleaner to write and read. I find c++ over verbose and complex. I'm constantly having to lookup what some symbol means next to some defined type. The header or code file separation complicates the project for me. I get lost/frustrated very quickly.

3. Good standard libraries

Using a c# library (say xml parsing) seems much nicer and cleaner. I may be wrong on this, I didn't actually use c++ for xml parsing, but it didn't look as easy or clean as c#.
C# also has really good UI libraries built in. I built a good GUI picker in wpf recently, using the .net sdk for node selection and scene callbacks. All of it is saved to xml. It was nice to do.
When I looked into doing the same thing in c++ - with the same ui features wpf offers out of the box - I was put off very quickly.


For my current needs, c++ is a big over complication. I just need a clean fast to write language that allows me to do lots of things. Maya python was a huge break-though for me, coding wise, but I see now I was spoiled by the decent implementation .
In max if I need to write a tool or some code that needs to be reasonably fast (something that maxscript collapses under) c# is more attractive over c++.

All that being said, I know, I know, I just spent the last few days working out some small, stupid discrepancy... I know. Which is the better trade off? I'm not sure. I also have not tried implementing a full derived plugin in c# yet (a custom controller or modifier ect) so that could be a big factor for future development.

Feel free to chastize me if you think I'm way off on my reasons/perceptions
 
Old 12 December 2013   #10
I won't chastise you each to there own and all that and what ever gets the job done. But let me at least give the case for the defense.

I find debugging c++ is one of the real pluses of using the sdk a proper debugger with break points or conditional break points, variable watches, memory dumps etc is a must have for any serious developer.

The performance is outstanding, I 've got a vertex colour noise modifier that shades verts like you can't believe.

Yes the projects can be a pain to set up but you only need possibly 6 projects (though these could be combined if you wanted) as each project can contain any number of plugins... I currently have the following for my current tools suite....

Objects Project
Modifiers Project
MXS Extension Project
Tools Project
Materials/Texture Maps Project
Import/Export Project

unless it's a stand alone plugin project for distribution. And once set up the actual process of adding a new plugin is not difficult it's more tedious copy paste find and replace boiler plate code work. I now even prefer the sdk for creating the ui where once upon of time thought mxs had the edge for ease of creating.

I don't get the c# is a simpler coding language it's just a different coding language as far as I can see and as for the Good standard libraries, but they are all usable from c++ using visual studio as ms added some non standard extension to the language that's why .net has c++, c# and visual basic example tabs on the MSDN website.

here's the c++ .Net listbox example
void button1_Click( Object^ /*sender*/, System::EventArgs^ /*e*/ )
{

	// Create an instance of the ListBox.
	ListBox^ listBox1 = gcnew ListBox;
	
	// Set the size and location of the ListBox.
	listBox1->Size = System::Drawing::Size( 200, 100 );
	listBox1->Location = System::Drawing::Point( 10, 10 );
	
	// Add the ListBox to the form. 
	this->Controls->Add( listBox1 );
	
	// Set the ListBox to display items in multiple columns.
	listBox1->MultiColumn = true;
	
	// Set the selection mode to multiple and extended.
	listBox1->SelectionMode = SelectionMode::MultiExtended;
	
	// Shutdown the painting of the ListBox as items are added.
	listBox1->BeginUpdate();
	
	// Loop through and add 50 items to the ListBox. 
	for ( int x = 1; x <= 50; x++ )
	{
	 listBox1->Items->Add( String::Format( "Item {0}", x ) );
	
	}
	listBox1->EndUpdate();
	
	// Select three items from the ListBox.
	listBox1->SetSelected( 1, true );
	listBox1->SetSelected( 3, true );
	listBox1->SetSelected( 5, true );
	
	#if defined(DEBUG)
	// Display the second selected item in the ListBox to the console.
	System::Diagnostics::Debug::WriteLine( listBox1->SelectedItems[ 1 ] );
	
	// Display the index of the first selected item in the ListBox.
	System::Diagnostics::Debug::WriteLine( listBox1->SelectedIndices[ 0 ] );
	#endif
}
 
Old 12 December 2013   #11
Thread automatically closed

This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.
 
Thread Closed share thread



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
CGSociety
Society of Digital Artists
www.cgsociety.org

Powered by vBulletin
Copyright 2000 - 2006,
Jelsoft Enterprises Ltd.
Minimize Ads
Forum Jump
Miscellaneous

All times are GMT. The time now is 03:15 PM.


Powered by vBulletin
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.