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.

404