Articles by " LoneRobot"

New Script! Scene Bracket

May 21, 2011 by     3 Comments    Posted under: DotNet


I’ve been going through a folder on my hard drive and seeing if there are any useful scripted nibbles that I can release to the community. This is the first, and is a script that has been used in production  for many, many, years.

It simply allows you to set up a pair of frame tags that store the current time frame you are working in. If you need to move the timeslider range to see some other animation keys, you simply press the set button to return to the stored range. This is an invaluable time saver in animation production as ranges change for each shot in a length of scene audio.

The dialog is very small too, meaning it can sit in the trackbar and not clutter up the screen space (see picture above)

Usage –

0 Key jumps the timeslider start point to frame 0

> xxxf key moves the time slider to the start time

< xxxf key moves the time slider to the end time > Set < Returns the animation range to the stored range. By clicking this button, you can replace the stored range to the current frame range.

m click and drag the dialog to a different location.

x closes the dialog

I hope you find it useful, It may not look like much, but many of the animators swear by it!

End User Event 2011

Apr 23, 2011 by     2 Comments    Posted under: DotNet

Hi all. Just a short post to promote a great event that I am involved in this year.

eue logo 

The End User Event is a no-nonsense get together for people working inside the Media & Entertainment industry. 2 days packed with workshops and master classes with national and international speakers.

This year’s End User Event will be held in Utrecht at Florin. Florin used to be an old bank building which was transformed into a pub and separate conference rooms with an informal atmosphere.

Main purpose of the End User Event is sharing knowledge and networking in an informal setting. The 20 expert speakers will educate, explain productions and give loads off tips and tricks.

I will be doing a talk to discuss creative technical direction, with a focus on showing how even a small studio team can develop and benefit from a strong character pipeline by creating flexible project tools.

There are some amazing speakers there this year so I am very humbled to be asked to speak. The event is organised by 3DStudio.nl and Joep Van Den Steen, who rendering fans will know wrote the book Rendering With Mental Ray and 3ds Max; co-authored with Ted Boardman, who is also speaking at the event.

I am very excited about meeting everyone, but the fact that the Godfather of MaxScript, Borislav Petrov is speaking  is worth the entrance fee by itself.

There are lots of announcements to come on the event program, so make sure you bookmark the site and check back!

http://www.enduserevent.com/

At what point…

Apr 14, 2011 by     7 Comments    Posted under: Maxscript, News

Do you realise that you’ve been writing maxscript for too long….?

Keyboard

DotNet Demystified : Part 1

Mar 27, 2011 by     14 Comments    Posted under: DotNet

I get a fair few requests asking if I could do some beginner’s tutorials about how to use dotnet in 3dsMax. It seems that a lot of people would like to get a handle on how to use the language properly. It’s actually no surprise to me that this is the case  – I also found at the start that If approached in the wrong way, dotnet and maxscript is a confusing and verbose place,  much like IKEA on the first day of school holidays.

What I’ll try to do in these tutorials is not to give you a series of instructions on ‘what to type in max in order to use dotnet’, but give you a background into –

  • How it works
  • What you are looking for
  • Where to find answers when you are not sure of how to proceed.

There are dotnet tutorials that exist already, like on Paul Neale’s site that gives information on how to use various dotnet controls. This is all great information already, so there’s no real point to me replicating this. What my focus is in these articles is to deviate from my normal article and give you a background into the dotnet world from a beginner’s perspective.

I’ll try to keep the programming jargon to a minimum, but be warned, you will have to accept that there will be some new terms to learn. So with that in mind, lets kick off with some jargon from the word go….

Getting to grips with what ‘Object Oriented Programming’ actually means.

I say this, because not everyone approaches this from a programming background. Let’s assume that, like me, you were an artist originally and got into scripting as a means to write tools to aid you in your creative process. Now the first thing that most people will say is ‘Yeah, I know what Object Oriented (OO) is, move onto the next lesson already…” But let’s just pause there and consider what it actually is. Remember, Maxscript has some level of OO but it isn’t a true OO language.

‘Object Oriented’ will obviously mean that it uses objects, but let’s clarify what an object is. In a 3d program, an object can be many things – a shape, a primitive, a light, a camera. Its easy to relate to this as an object because you can see it, and set various visual aspects through the modifier stack.

A teapot, for example is a programmatical description of everything the computer needs in order to display a teapot in the viewport. In programming terms, this description is known as a class, and it’s a blueprint of how the particular object needs to be made. In order to make a teapot in 3dsMax, it normally looks for something known as a constructor. You might have read this term before – you’ll see it in the maxscript help at the start of any listing. It basically means the command that you have to call in order to create the particular object.

teapot

In the teapot’s case, it is the word ‘teapot’, so that’s about as straight forward as it gets. However, a constructor is actually a method, and methods need to be executed by adding a set of parentheses at the end, exactly like a function.

When you call the teapot constructor you make an instance of the teapot class. This means that it creates a copy of the teapot from the instructions in the class. This is a process known as instantiation. Until the class is instantiated, there is no object. You can see this by typing teapot into the max listener –

  teapot
  Teapot
  teapot()
  $Teapot:Teapot01 @ [0.000000,0.000000,0.000000]

When you type teapot it just returns the class name, verifying that it exists. It is only when you call the constructor (by adding the parentheses) that the class is instantiated. In simple terms, you have made a teapot.

Okay, so where does this fit in with dotnet? You might be getting annoyed that I haven’t even mentioned anything about using dotnet yet. But this is the key to using it – Once you have embraced the Idea of what object oriented programming actually is, you will be able to take greater steps with dotnet. So the parallels between the teapot example in maxscript is very similar to how you should approach programming with dotnet.

So let’s recap so far –

blueprint

With a teapot, the constructor is very simple – the default constructor creates a teapot with default parameters, radius and segments. We can set these after the teapot is made, by using a property call. So if we set a value to the radius, the teapot changes size. We might want that from the start, so the teapot constructor also accepts multiple arguments.

But let’s also imagine that the appearance of the teapot is something that we want to organise for ourselves. In dotnet, we might decide that the way a teapot looks would merit having an object to specifically organise this information. So in order to acheive this, we might write a class to handle this. Lets call this the geometry_appearance class.  In this class, there would be the properties we needed like radius and segments. So in this example, the teapot would require a geometry_appearance class in order to create one. So how do we do this? well, it’s a class so you see we treat it in exactly the same way – by instantiating it.

So we have an instance of the geometry_appearance class which can be passed to the teapot constructor. No big deal, we still just get a teapot. But hang on, a sphere has properties like radius and segments. We could in theory use the instance of geometry_appearance and pass it to a sphere to create a sphere with the same number of segments and radius. So the power of the geometry_appearance class is that it can be used to create many different objects, depending on it’s design.

pseudo-class

This is of course hypothetical, these objects are not created like this in maxscript, but when you start looking into dotnet classes, you will start to find that there are properties that you can set to numerical values and boolean values (true and false) but there are just as many properties that can only be set by supplying other classes.

Take the dotnet label for example. We know the difference between a class and an object now, so we know if we called the command dotnetclass on label, we would just get the properties of the class, not the object. When you ask a dotnetclass information about itself, it will display the class name in between brackets like these –  <>.

lbl = dotnetclass "label"
dotNetClass:System.Windows.Forms.Label
  show lbl
  .CheckForIllegalCrossThreadCalls : <System.Boolean>, static
  .DefaultBackColor : <System.Drawing.Color>, read-only, static
  .DefaultFont : <System.Drawing.Font>, read-only, static
  .DefaultForeColor : <System.Drawing.Color>, read-only, static
  .ModifierKeys : <System.Windows.Forms.Keys>, read-only, static   .MouseButtons : <System.Windows.Forms.MouseButtons>, read-only, static   .MousePosition : <System.Drawing.Point>, read-only, static
true

By calling the object method, we actually create a label object. Look at the amount of properties now!

lbl = dotnetobject "label"
dotNetObject:System.Windows.Forms.Label
show lbl
  .AccessibilityObject : , read-only
  .AccessibleDefaultActionDescription :
  .AccessibleDescription :
  .AccessibleName :
  .AccessibleRole :
  .AllowDrop :
  .Anchor :
  .AutoEllipsis :
  .AutoScrollOffset :
  .AutoSize :
  .BackColor :
  .BackgroundImage :
  .BackgroundImageLayout :
  .BindingContext :
  .BorderStyle :
  .Bottom : , read-only
  .Bounds :
  .CanFocus : , read-only
  .CanSelect : , read-only
  .Capture :
  .CausesValidation :
  .CheckForIllegalCrossThreadCalls : , static
  .ClientRectangle : , read-only
  .ClientSize :
  .CompanyName : , read-only
  .Container : , read-only
  .ContainsFocus : , read-only
  .ContextMenu :
  .ContextMenuStrip :
  .Controls : , read-only
  .Created : , read-only
  .Cursor :
  .DataBindings : , read-only
  .DefaultBackColor : , read-only, static
  .DefaultFont : , read-only, static
  .DefaultForeColor : , read-only, static
  .DisplayRectangle : , read-only
  .Disposing : , read-only
  .Dock :
  .Enabled :
  .FlatStyle :
  .Focused : , read-only
  .Font :
  .ForeColor :
  .Handle : , read-only
  .HasChildren : , read-only
  .Height :
  .Image :
  .ImageAlign :
  .ImageIndex :
  .ImageKey :
  .ImageList :
  .ImeMode :
  .InvokeRequired : , read-only
  .IsAccessible :
  .IsDisposed : , read-only
  .IsHandleCreated : , read-only
  .IsMirrored : , read-only
  .LayoutEngine : , read-only
  .Left :
  .Location :
  .Margin :
  .MaximumSize :
  .MinimumSize :
  .ModifierKeys : , read-only, static
  .MouseButtons : , read-only, static
  .MousePosition : , read-only, static
  .Name :
  .Padding :
  .Parent :
  .PreferredHeight : , read-only
  .PreferredSize : , read-only
  .PreferredWidth : , read-only
  .ProductName : , read-only
  .ProductVersion : , read-only
  .RecreatingHandle : , read-only
  .Region :
  .Right : , read-only
  .RightToLeft :
  .Site :
  .Size :
  .TabIndex :
  .TabStop :
  .Tag :
  .Text :
  .TextAlign :
  .Top :
  .TopLevelControl : , read-only
  .UseCompatibleTextRendering :
  .UseMnemonic :
  .UseWaitCursor :
  .Visible :
  .Width :
  .WindowTarget :

When 3dsmax uses a dotnetclass, it can perform certain things without having to change how you would do them is maxscript. So for example, the text property on a label is listed as

.Text : <System.String>

So you can see that text takes a string as a property. You should notice that it is a system.string, which means it’s actually a dotnet string, not a maxscript string. These are both strings but they are different data types. Good news is that maxscript automatically converts the maxscript string into a dotnet one, so you dont have to do anything special. This is the same for booleans and values like integers and floats. (a float in dotnet is known as a single) In programming, this is known as casting.

blueprint-cast

However, while maxscript can cast a maxscript string into a dotnet string, it can’t do something like this –

.Size : <System.Drawing.Size>

Rather than have a width and height property, a dotnet label has a size. Like the hypothetical teapot example, it has a class that decides how it is supposed to be.  So to set the size of the label, we need to instantiate the size class. So the create this class into an object, surely we need to call the same dotnetobject method in max?

sz = dotnetobject "System.Drawing.Size"
  -- Runtime error: No constructor found which matched argument list: System.Drawing.Size

in this case, no. The size class’s constructor needs some other parameters in order to instantiate it. So how do we know what these are? In maxscript you can use the command :

dotnet.showconstructors dotnetclass

So remember, we are not operating on the object yet, because we haven’t made it – a class has the constructor, not the object so we need to pass that –

dotnet.showconstructors (dotnetclass "System.Drawing.Size")
System.Drawing.Size <System.Drawing.Point>pt
System.Drawing.Size <System.Int32>width <System.Int32>height

Bingo! here’s the information that you need. This means that the size class actually has two possible constructors. The first is actually saying that you could pass it an instance of the System.drawing.point class to call the constructor. The second is saying that it needs two properties – width and height. As you can see, these are instances of the integer class. (System.Int32) Fortunately, maxscript casts integers into dotnet integers without a problem so you can instantiate the size class with two numbers.

lbl = dotnetobject "label"
  dotNetObject:System.Windows.Forms.Label

sz = dotnetobject "System.Drawing.Size" 20 20
dotNetObject:System.Drawing.Size
lbl.size = sz
dotNetObject:System.Drawing.Size

So there you have it, Some properties will need classes to specify what happens to them, and those classes might even have their own classes for construction. But just remember where you are in terms of creating the object and read what the listener is telling you. It really does give you most of the information that you need.

So where can this fall down? Let’s look at another example and explain what is going on. Say we wanted to set the label’s backcolor property –

.BackColor : <System.Drawing.Color>

Lets try making an instance of the color class.

dotnetobject "system.drawing.color"
  -- Runtime error: No constructor found which matched argument list: system.drawing.color

Ok, so it’s saying it needs a constructor that we havent supplied.

dotnet.showconstructors (dotnetclass "system.drawing.color")
  false

That’s not helping us like the last time.

columbo-1

Let’s try a showproperties call on the class itself.

show (dotnetclass "system.drawing.color")
  .AliceBlue : , read-only, static
  .AntiqueWhite : , read-only, static
  .Aqua : , read-only, static
  .Aquamarine : , read-only, static
  .Azure : , read-only, static
  .Beige : , read-only, static
  .Bisque : , read-only, static
  .Black : , read-only, static
  .BlanchedAlmond : , read-only, static
  .Blue : , read-only, static
  .BlueViolet : , read-only, static
  .Brown : , read-only, static
  .BurlyWood : , read-only, static
  .CadetBlue : , read-only, static
  .Chartreuse : , read-only, static
  .Chocolate : , read-only, static
  .Coral : , read-only, static
  .CornflowerBlue : , read-only, static
  .Cornsilk : , read-only, static
  .Crimson : , read-only, static
  .Cyan : , read-only, static
  .DarkBlue : , read-only, static
  .DarkCyan : , read-only, static
  .DarkGoldenrod : , read-only, static
  .DarkGray : , read-only, static
  .DarkGreen : , read-only, static
  .DarkKhaki : , read-only, static
  .DarkMagenta : , read-only, static
  .DarkOliveGreen : , read-only, static
  .DarkOrange : , read-only, static
  .DarkOrchid : , read-only, static
  .DarkRed : , read-only, static
  .DarkSalmon : , read-only, static
  .DarkSeaGreen : , read-only, static
  .DarkSlateBlue : , read-only, static
  .DarkSlateGray : , read-only, static
  .DarkTurquoise : , read-only, static
  .DarkViolet : , read-only, static
  .DeepPink : , read-only, static
  .DeepSkyBlue : , read-only, static
  .DimGray : , read-only, static
  .DodgerBlue : , read-only, static
  .Firebrick : , read-only, static
  .FloralWhite : , read-only, static
  .ForestGreen : , read-only, static
  .Fuchsia : , read-only, static
  .Gainsboro : , read-only, static
  .GhostWhite : , read-only, static
  .Gold : , read-only, static
  .Goldenrod : , read-only, static
  .Gray : , read-only, static
  .Green : , read-only, static
  .GreenYellow : , read-only, static
  .Honeydew : , read-only, static
  .HotPink : , read-only, static
  .IndianRed : , read-only, static
  .Indigo : , read-only, static
  .Ivory : , read-only, static
  .Khaki : , read-only, static
  .Lavender : , read-only, static
  .LavenderBlush : , read-only, static
  .LawnGreen : , read-only, static
  .LemonChiffon : , read-only, static
  .LightBlue : , read-only, static
  .LightCoral : , read-only, static
  .LightCyan : , read-only, static
  .LightGoldenrodYellow : , read-only, static
  .LightGray : , read-only, static
  .LightGreen : , read-only, static
  .LightPink : , read-only, static
  .LightSalmon : , read-only, static
  .LightSeaGreen : , read-only, static
  .LightSkyBlue : , read-only, static
  .LightSlateGray : , read-only, static
  .LightSteelBlue : , read-only, static
  .LightYellow : , read-only, static
  .Lime : , read-only, static
  .LimeGreen : , read-only, static
  .Linen : , read-only, static
  .Magenta : , read-only, static
  .Maroon : , read-only, static
  .MediumAquamarine : , read-only, static
  .MediumBlue : , read-only, static
  .MediumOrchid : , read-only, static
  .MediumPurple : , read-only, static
  .MediumSeaGreen : , read-only, static
  .MediumSlateBlue : , read-only, static
  .MediumSpringGreen : , read-only, static
  .MediumTurquoise : , read-only, static
  .MediumVioletRed : , read-only, static
  .MidnightBlue : , read-only, static
  .MintCream : , read-only, static
  .MistyRose : , read-only, static
  .Moccasin : , read-only, static
  .NavajoWhite : , read-only, static
  .Navy : , read-only, static
  .OldLace : , read-only, static
  .Olive : , read-only, static
  .OliveDrab : , read-only, static
  .Orange : , read-only, static
  .OrangeRed : , read-only, static
  .Orchid : , read-only, static
  .PaleGoldenrod : , read-only, static
  .PaleGreen : , read-only, static
  .PaleTurquoise : , read-only, static
  .PaleVioletRed : , read-only, static
  .PapayaWhip : , read-only, static
  .PeachPuff : , read-only, static
  .Peru : , read-only, static
  .Pink : , read-only, static
  .Plum : , read-only, static
  .PowderBlue : , read-only, static
  .Purple : , read-only, static
  .Red : , read-only, static
  .RosyBrown : , read-only, static
  .RoyalBlue : , read-only, static
  .SaddleBrown : , read-only, static
  .Salmon : , read-only, static
  .SandyBrown : , read-only, static
  .SeaGreen : , read-only, static
  .SeaShell : , read-only, static
  .Sienna : , read-only, static
  .Silver : , read-only, static
  .SkyBlue : , read-only, static
  .SlateBlue : , read-only, static
  .SlateGray : , read-only, static
  .Snow : , read-only, static
  .SpringGreen : , read-only, static
  .SteelBlue : , read-only, static
  .Tan : , read-only, static
  .Teal : , read-only, static
  .Thistle : , read-only, static
  .Tomato : , read-only, static
  .Transparent : , read-only, static
  .Turquoise : , read-only, static
  .Violet : , read-only, static
  .Wheat : , read-only, static
  .White : , read-only, static
  .WhiteSmoke : , read-only, static
  .Yellow : , read-only, static
  .YellowGreen : , read-only, static
  .Empty : , read-only, static

Whoa, result. So why is this showing all of the color properties without instantiating the class first?

This is because the color class is a static class. A static class means that the properties and functions can be utilised without having to make an instance of the class. If we wanted a color that was predefined in the class – like YellowGreen, we could use one of these properties.

However, remember these are just the properties of the class which in this case are a load of color presets. We might also need to call showmethods too.

showmethods (dotnetclass "system.drawing.color")
  .[static]Equals objA objB
  .[static]FromArgb argb
  .[static]FromArgb alpha baseColor
  .[static]FromArgb red green blue
  .[static]FromArgb alpha red green blue
  .[static]FromKnownColor color
  .[static]FromName name
  .[static]ReferenceEquals objA objB
true

You can see that these are static methods, so do not need a class instance. From this, we can deduce that the color class can be used with the .fromARGB method which has numerous methods. We can use this to specify an RGB value –

.[static]<System.Drawing.Color>FromArgb
System.Int32>red
System.Int32>green
System.Int32>blue

So we can use this in max in the following syntax –

(dotnetclass "system.drawing.color").fromARGB 255 0 0
  dotNetObject:System.Drawing.Color

You will encounter static classes from time to time. Most notably with the image class. This is used in the same way. You’ll see it is static as soon as you dig in to find information about what you’re doing with it. As soon as you see the static keyword, it means you’ll be using the class itself and not the instantiated object.

showmethods (dotnetclass "system.drawing.image")
  .[static]Equals objA objB
  .[static]FromFile filename
  .[static]FromFile filename useEmbeddedColorManagement
  .[static]FromHbitmap hbitmap
  .[static]FromHbitmap hbitmap hpalette
  .[static]FromStream stream
  .[static]FromStream stream useEmbeddedColorManagement
  .[static]FromStream stream useEmbeddedColorManagement validateImageData
  .[static]GetPixelFormatSize pixfmt
  .[static]IsAlphaPixelFormat pixfmt
  .[static]IsCanonicalPixelFormat pixfmt
  .[static]IsExtendedPixelFormat pixfmt
  .[static]ReferenceEquals objA objB

That’s about all for part one of the tutorial. I hope it’s explained things in a concise way, I’ll be adding to these over the next few weeks and introducing other aspects of dotnet so that you can start to use it more efficiently. If you have any aspects of DotNet that you cant get a handle on, contact me through the form on this site and let me know – I can add them to a DotNet FAQ.

Photoshop Automation Project Update

Mar 21, 2011 by     No Comments    Posted under: Characters, DotNet, Imaging, Program Automation, Technical Research

Veteran LR.net readers will know that a few years back I published a research project into using managed code to control Photoshop using COM interop. If you are not sure of the article in question, then you can read it here.

It’s been a popular article, so it is with great pleasure that I am now publishing an update. Now that my baby girl is sleeping through the night I’m actually in a position to think clearly about programming again. The new assembly contains a few bug fixes (namely the save option didn’t work properly, oops) and some new methods. The full list can be seen in the class layout at the end of the article.

New methods –

Channels_AlphatoLayerMask()

Channels_DeleteAlpha()

Convert_ImagetoCMYK()

Convert_ImagetoGrayscale()

Convert_ImagetoIndexedColor()

Convert_ImagetoLabColor()

Convert_ImagetoRGB()

Paths_DeleteAll()

Selection_ActivateBottomLayer()

Selection_ActivateTopLayer()

Selection_CreateNewDocumentFrom()

Selection_FromAlpha()

Selection_SelectLayerContents()

Most of these should be self explanatory. I have chained a few of these new functions into a new automation –

Automation_CutOut()

On my latest batch of characters, I was making 2D cut-out style characters based on shots of real people. These PS files were constructed from a green screen shoot of the band Blue Soup.

Blue Soup

In order to get these assets into 3dsMax in order to rig the characters, each layer would have to be selected, pasted into a new document and saved. Then a work path would have to be created and exported. Not a difficult operation, but with four characters (and an average of twenty layers each) is a reasonably time consuming one. Here’s a screen grab of the action working – If you are thinking that the process takes a while on some layers, it’s because the Photoshop file is composed from 6K RAW images shot on a Canon 5D Mk2, and my old dell laptop.

So there you are, a nice time-saving action that is controlled completely via managed code. I invoked this via my test project in VS2008, but it could easily be fired off via 3dsMax itself.

Full class listing –

And as always, download the assembly below –

download