Articles by " LoneRobot"

What we are today comes from our thoughts of yesterday

Apr 11, 2012 by     5 Comments    Posted under: DotNet

Apologies for a slightly existential tone to my latest blog post – I was reading the excellent blog by Maya TD Hamish Mackenzie, and it did get me thinking. It’s a strange conclusion to come to when you realise that the thing that you were learning to make your life as an animator easier ends up taking over and stopping you from doing something that you love.

But that’s just one side of the argument.

In order to get to that stage, there is a choice that is made over which discipline to focus on. You have to be serious because there is no short learning curve in 3D. Any choice you make about career direction has to be for the same reason that you started anything in this industry to begin with – because you love doing it. Perhaps, part of the consternation is the idea that programming isn’t quite as glamorous as a more signposted artistic discipline. But there is no doubt that a TD requires a balance of logic and creativity that is difficult to find, as they are not natural bedfellows. Not only are they difficult to find, they are harder to draw the line between. Are you an artist who codes, or a coder who does art? Sometimes you are both of these. Your brain will tell you if you’re straying more towards the side you dont want to be. Hamish wrote in his post :

…doing animation is something that I think is really important for me as a technical animator.  Using the tools I’ve written, working through the workflows I’ve helped define, having to deal with all the bugs, shortcomings etc of the work environment I’ve helped create puts me in the shoes of my users and forces me to see and understand the implications of my design decisions.  And I think thats really important.

I’d have to agree – I certainly can’t think the same about animation in the way I may have once. I am too hardwired to see bottlenecks and tool potential with any process to allow me to spend the single minded application necessary these days. Hamish said it perfectly :

But the other big thing is the fact that its hard to stop thinking about the technical side of things while I’m animating.  Its been such a part of my mental process for so long that its really hard to turn it off.  And I think this is the biggest thing.  So many parts of the animating process have the potential to be improved, sped up, optimized, made easier etc…  And when I’m animating, all these thoughts are running through my head.  So just pushing those thoughts aside – or at least shelving them for later – is hard.

Many years ago I graduated with a degree in Sculpture. The notion that 10 or so years down the line I would be figuring out custom event delegates in C# wouldn’t have been in the forefront of my mind.  It’s just ironic that you’ve ended up becoming the thing that you wouldn’t actually choose to call yourself. So essentially, us lot are a strange breed. We live in code and dream of drawing. We camelCase shopping lists and hate the fact that we now spell colour, color.

I guess the important thing would be to make sure that you never lose your creative side, and channel that into your new trade.

Be Deep Bop De Doop 010101 #!-$Z#!

 

Autodesk Webinar

Apr 10, 2012 by     No Comments    Posted under: DotNet

Despite a nagging suspicion that “Webinar” is a made up word, I will be speaking with my colleague Ben Robins about some of our work at Nexus and how 3dsmax features in our animation pipeline.

Details can be found here :

Jamies Jewels

Autodesk site

Two Clicks From Amsterdam

Oct 2, 2011 by     2 Comments    Posted under: 3dsMax, Characters, DotNet, Maxscript, Tips and Tricks

End User Event Roundup

 

http://www.enduserevent.com/

Yes it’s a little on the late side. Due to me starting a new job on the following Monday at Nexus in London my EUE write up has been a little delayed.

So what is EUE? If you don’t already know, it’s a 3d event that has been going for a few years now that allows like-minded 3d enthusiasts and proffesionals to all get together and attend talks. This is all great, but the cherry on the top is when you realise that it’s hosted in a PUB in Utrech.

 

It was an absolute honour to be asked to speak there this year. There were too many great people that I met to thank them all, but a special mention goes to Bobo, Rune Spaans and Ted Boardman (Both just about the nicest people you’d ever meet in the 3d industry) and to the people I hung out with over the weekend – Johan Boekoven, Yoni Cohen and Gonzalo Rueda. Also, massive thanks extend to Joep Van Den Steen and Michiel Quist who organised the whole thing, and of course to Jamie and Shane from Autodesk who were all great guys.

My talk at EUE was to not just showcase a few of the animation systems that I have written over the last few years, but to get across  the many potential stages of development for tools. Whether you are writing a line of code to make your life easier or trying to establish a pipeline, good tools will persist and ill-thought out ones will not last even one production. There is no correlation between the complexity or the number of lines of code in the success of a tool. Your animators will decide!

MaxScript Lesson Ahoy!

One of the tools that I have tended to re-use over many productions is the LayerControl script. It allows animators to bypass the layer manager and hide layers according to object type. While there are a few ways of achieving this on a node level using AppData or UserProps, you can quickly setup a system like this with a methodical and consistent naming convention. When working, hiding and showing rig controls and meshes on masse become a simpler and quicker affair. This might not sound much, but over a long animation process tools like this save time.

Lets look at how to set this system up.

Firstly, at the rigging stage, you will want to make sure that all of your characters have consistent layer names.

It is the suffixes of these layers that the script will be using to control whether a layer is hidden or not.

Script Stage 1

-- Basic Layer Visibility Control

( local str = “MESH” local ishidden = true for i= 1 to (LayerManager.count-1) where ( (dotnetobject “system.string” (LayerManager.getLayer i).name).endswith str ) do (LayerManager.getLayer i).ishidden = ishidden )

This is a basic piece of code that will hide any layer with the suffix “MESH”. As it stands, its not really useful for anything except to illustrate the code of the script. Note that this has been formatted this way in order to make it clearer, you could add it all into one line. Using where in a loop is a useful trick. In this case, it allows us to perform the hiding of the layer without having to collect the Mesh layers into an array and iterating that. You’ll notice I use a dotnet string method in this. You could just as easily use :

matchpattern (LayerManager.getLayer i).name pattern:(“*”+str)

The dotnet method might be fractionally slower to execute, but since we’re talking a few milliseconds, it’s not going to make a whole lot of difference. I included it to illustrate the dotnet string obejct. While maxscript string methods are great, there are even more methods available to you via the dotnet methods should you need it. Use whichever one you like, it’s not going to make the final script any better or worse. If you’re just getting into scripting and programming, you’ll find a lot of the time you’ll want to make sure the core of the script is working before fiddling around with the UI.

Script Stage 2

The first script was just to establish how the method for hiding and unhiding will work.  The next version builds a basic UI and starts to add the functionality we want. It now works by calling a function that takes two arguments. For novice scripters, a function is something used widely in programming to represent and operation that you will want to re-use multiple times. As a form of shorthand, you just call the function name and pass the values it requires rather than typing the same code out repeatedly. In function calls the extra values are known as arguments. In this case, the suffix string that we want to hide/unhide is the first argument. The second argument is to decide whether the function will hide or unhide the layer. Since this is an either/or type, we use a boolean argument of true or false. So in this code we use a button click to hide the layer (passing the string and the value true) and the button’s right click handler to pass the same string and false to unhide it.

try(destroydialog HideRigs)catch()

rollout HideRigs "" width:73 height:84
(
	fn LayerVisibiltyBySuffix str ishidden =
	(
	for i= 1 to (LayerManager.count-1) where ((dotnetobject "system.string" (LayerManager.getLayer i).name).endswith str) do ((LayerManager.getLayer i).ishidden = ishidden)
	)

	button btnRIG "Rig" pos:[2,56] width:67 height:23	border:false
	button btnMESH "Mesh" pos:[3,30] width:67 height:23 border:false
	button btnCTRLS "Controls" pos:[3,3] width:67 height:23 border:false

	on btnRIG pressed do LayerVisibiltyBySuffix "RIG" true
	on btnCTRLS pressed do LayerVisibiltyBySuffix "CTRLS" true
	on btnMESH pressed do LayerVisibiltyBySuffix "MESH" true

	on btnRIG rightclick do LayerVisibiltyBySuffix "RIG" false
	on btnCTRLS rightclick do LayerVisibiltyBySuffix "CTRLS" false
	on btnMESH rightclick do LayerVisibiltyBySuffix "MESH" false	

)
createdialog HideRigs

Script Stage 3

Stage 3 has some improvements in the form of replacing the max controls with some dotnet controls. Have read of the code and I’ll discuss what s going on afterwards.

macroScript ShowHideLayers
category:"LoneRobot"
toolTip:"Show Hide Layers"
buttontext:"Layers"
(
	try(destroydialog HideRigs)catch()

	rollout HideRigs "" width:84 height:324
	(
		local DotNetColorMan = (dotnetclass "managedservices.cuiupdater").getinstance()
		local MlbSelection = #("Angus", "Big_Pig", "Bo", "Cow", "Cowhand_One", "Cowhand_Two", "Crow", "Farmer", "FarmGirl", "Hebaa", "Leonard", "Mini", "Piggy", "Shebaa", "Trinny", "Trotski", "Unicorn", "Winnie")

		fn LayerVisibiltyBySuffix str lbx ishidden =
		(
		if 	lbx.selection.isEmpty then
			(
				for i= 1 to (LayerManager.count-1) where ((dotnetobject "system.string" (LayerManager.getLayer i).name).endswith str) do ((LayerManager.getLayer i).ishidden = ishidden)
				enableaccelerators = true
			)
			else
				(
				for each in lbx.selection  do
					(
					local lay = LayerManager.getLayerfromname (lbx.items[each] +"_"+ str)
					if lay !=undefined then lay.ishidden = ishidden
					enableaccelerators = true
					)
				)
		)

		fn MouseButton args = dotNet.compareEnums args.button (dotnetclass "System.Windows.Forms.MouseButtons").left

		dotNetControl btnRIG "button" pos:[1,56] width:82 height:23
		dotNetControl btnMESH "button" pos:[1,30] width:82 height:23
		dotNetControl btnCTRLS "button" pos:[1,3] width:82 height:23
		Multilistbox lbxCh "" pos:[1,82] width:82 height:18 items:MlbSelection

		on HideRigs open do
		(
		btnRIG.flatstyle = btnMESH.flatstyle= btnCTRLS.flatstyle =(dotNetclass "System.Windows.Forms.FlatStyle").Flat
		btnRIG.backcolor = btnMESH.backcolor= btnCTRLS.backcolor = DotNetColorMan.GetControlColor()
		btnRIG.forecolor = btnMESH.forecolor= btnCTRLS.forecolor = DotNetColorMan.GetTextColor()
		btnRIG.text = "Rig"
		btnMESH.text = "Mesh"
		btnCTRLS.text = "Controls"
		)

		on btnRIG mouseDown sender args do LayerVisibiltyBySuffix "RIG" lbxCh (MouseButton args)
		on btnMESH mouseDown sender args do LayerVisibiltyBySuffix "MESH" lbxCh (MouseButton args)
		on btnCTRLS mouseDown sender args do LayerVisibiltyBySuffix "CTRLS" lbxCh (MouseButton args)

		on lbxCh rightclick do lbxch.selection = #{}

	)
createdialog HideRigs
 )

You’ll see that there is only one handler for each button. So you may be wondering how it passes the required true/false argument with just one call. This is one reason why we use  dotentcontrols instead of max UI controls. When you click a dotnetcontrol you can get some additional provided about which mouse button you have used. This is contained within the args property. The mousedownevent calls a function before passing it’s final argument. This function returns the boolean argument we need. So before handling the mousedown event, it asks this function a question:

fn MouseButton args = dotNet.compareEnums args.button (dotnetclass "System.Windows.Forms.MouseButtons").left

this translates in plain speech to “is the mouse button clicked the left button?”. We use dotnet.compareenums to return either true or false in answering a comparision of the mouse button used to click each button and the enumeration of the left button. Dotnet uses enumerations to simplify data types rather than expecting the user to identify an arbitrary integer code.

The other addition to this script is to add a listbox of character names. These names are hardcoded into an array at the start of the script. You could easily scan a set of character folders and retrieve these names dynamically. You just need to make sure you are consistent with naming or the system will break down.

Script Stage 4

From now on, I wont be posting the entire code for each example, but highlighting the important parts of the new code. Don’t worry though, full code samples will be provided at the end of the post so you’ll be able to see what I mean.  Stage 4 brings in base64 encoded strings to store button bitmaps. There’s no point me going into this as i’ve posted about this before here. In normal circumstances, the base64 struct would be added to the startupscripts as a separate entity so that it executes only once on maxstart.

We have also ramped up the modifier key functionality. I’m personally a fan of having multiple functions on a single button – purely for the fact that you keep the UI as small as possible. There’s nothing worse than cramming a UI with extra buttons that could be easily passed to a shift click variation of the same button. In the case of the layer control, its critical to keep the footprint as small as possible and just add a button with If somebody doesn’t want to use a shft click or ctrl click then they won’t. If they do, you’ve already got the functionality in there. You can’t lose really. I’ve always considered that you’ve memorised a whole load of keyboard shortcuts up to this point, it wont hurt to memorise a couple more. The key is to make them match design patterns that already exist in the software. So if you have shift click to select all the objects in a particular layer, then shift + ctrl click should add these nodes to the current selection, exactly how max appends a ctrl select. It’s just a way of keeping things consistent.

So this list of modifer key functions are as follows –

Left Click – Hide Layer

Right Click – Unhide Layer

Shift Click – Select Nodes on layer

Shift Ctrl Click – Add nodes to selection

Shift Double Click – Isolate layer node selection

Alt-Left Click – Freeze layer

Alt-Right Click – Unfreeze layer

Ctrl – Alt Click – Perform an inverse action – i.e. If one character name is selected, do the hide/unhide action on all other layers EXCEPT the selected one.

Too many? my logic is if they are overkill, people won’t use them. Bear in mind all of the permutations above were as a result of animators asking for them over the course of many productions!

Script Stage 5

Okay, final polish time here. The dotnet multilistbox has been changed to ownerdraw mode. This is more advanced dotnet stuff but it means that you can take control of the appearance of a dotnetcontrol in ways where the original appearance properties do not do what you require.


on dnlbx DrawItem sender args do
	(
			if (dotNet.compareEnums args.state (dotnetclass "DrawItemState").Selected) then
			(
				-- draw the selected state of the listbox
				args.Graphics.FillRectangle selBrush args.bounds -- background colour
				args.Graphics.DrawString dnlbx.Items.Item[args.index] args.font brushes.purple  args.bounds.location.x args.bounds.location.y -- draw the text
			)
			else
			(
				args.DrawBackground() -- this is an inbuild call to draw the default background
				args.Graphics.DrawString dnlbx.Items.Item[args.index] args.font uTextBrush args.bounds.location.x args.bounds.location.y -- draw the text string
			)
			--	args.DrawFocusRectangle() -- draw the focus rectangle last
		)

The comments should explain what is going on.

Conclusion

So that’s it for my EUE talk roundup. I hope you’ve been able to get something out of it. If any part makes someone want to start coding useful tools to help their company productions then it’s all been worthwhile. For anyone reading this that attended my talk, thanks for turning up and not throwing anything. If you have any questions, feel free to contact me.

All versions of the script are available to download below.

If you want a PDF of the visual slide material that I displayed in the background, you can find it here

 

WindowBox Replanted

Jul 18, 2011 by     4 Comments    Posted under: DotNet

After sitting unloved on Scriptspot since last year, WindowBox has suddenly registered over 300 votes! Many thanks to everyone who pressed the plus button, and I’m glad you have all been finding it useful. The version on Scriptspot is now on v2.3 and this latest version has better 64bit support as well as listing the Slate Material Editor.

http://www.scriptspot.com/3ds-max/scripts/give-me-back-my-dialogs

 

 

 

 

 

Another  good window position script is also on Scriptspot too. Don’t forget to check out this script by Raymond H.Ingebretsen –

http://www.scriptspot.com/3ds-max/scripts/reset-3ds-max-windows

SpeechBot – A handy script to load and save morpher keys

Jun 20, 2011 by     3 Comments    Posted under: 3dsMax


 

Yakkety Yak, It is always said that careless talk can cost lives, but careless talk in production costs you time. Perhaps that is why they call them deadlines.

For many years, I have used the same system to deal with lipsync within production, and it is with great pleasure that I release it to the community now. This script is called Speechbot, and will take a character that uses a morph modifier for lipsync and save the data to XML. This can then be re-applied at a later stage.

My approach for years and many productions has been to lipsync everything before you begin animation (which is hardly ground breaking), but it does make most sense to do this first as it doesn’t interrupt the animation workflow. Over the years, this has worked well with all sizes of productions as you can assign the lipsync to junior animators who want to get an idea of the production before jumping in at the deep end. It also allows you to standardise the output as the people doing it become more familiar with the characters and you don’t get individual animators doing their scenes differently. Generally the lipsync at this stage is to provide the sense of the speech phonemes/visemes and not necessarily the facial performance so the artist is still free to adjust the keys and push the expressions further when they animate to the scene audio.

Being able to save the files to XML has helped on the larger productions too, as we have been able to outsource the lipsync to another animation company who only need to email their work to us at the end of each episode in the form of small XML data files. This is the only thing we have to worry about synchronising. We also worked with two other external companies who were doing animation. Again, they could be emailed the lipsync for their scenes and everything was cohesive and organised.

Why don’t you just use max’s Load/Save animation?

That’s a simple one – excluding the fact that load/save animaition is barely scriptable, it just doesn’t work reliably enough. In the dark, Pre-Speechbot days we tried using it and found that max would ask us to infrequently remap controller tracks when loading and saving to the same object in the scene! This behaviour was odd and rather inconvenient, but did force me into writing something that is targeted specifically to character lipsync. It was only recently when this exact issue was brought up by Martin Briedt on the Beta forum that I thought that others were finding this issue frustrating too.

Who can use this script?

People wanting to save and restore character lipsync in the form of morph data.

In fact, it can save any morph data from any object. Speechbot stores each channel incrementally, along with value data and custom key tangents. So if you base all of your characters along the same structure (ie the morh channels are the same shape target and in the same order) you can load the same lipsync or morph data onto different characters.

Who can’t use this script?

With the exception of Piers Morgan and N-Dubs, Nobody. Well, if you have a bone based facial system, you can’t. But since you can still set up a perfectly reasonable morph driven character mouth, it’s designed specifically for those types. I will probably have to develop a new system in the future that takes in the ability to store TM data from facial nodes as well as lipsync but that’s for another time.

Using the Interface

Speechbot is a macroscript, so you’ll have to drop it into your UI/Macroscripts directory. If you don’t know how to do this then you’ll have to look outside this article (there are many tutorials on how to install macroscripts but here is one)

SpeechbotV5UIex

 

 

 

 

 

 

 

 

 

 

 

The workflow for using Speechbot is simple –

  1. Select an object with a morpher modifer
  2. click the save button

sbsave

 

 

That’s it!

When you are ready to load the data back onto the character, select the object with the morpher modifier, choose the XML file and press load

sbload

 

 

Simples!

Object filter

SB_f The interface has a dropdown list of all objects in the scene with morpher modifiers applied to them, so you can pick from this dropdown rather than physically selecting it. There is a textbox where you can type common wildcard path searches, like *mouth* so that you only pick up objects with that text in their object name.

This helps if you have numerous morphers in a scene.

Working cache directory

In order to speed up the loading of lipsync data, Speechbot has a working cache directory. When you run the script, you’ll see that it will search a folder where it always looks for XML files to load. This can be set by clicking the folder icon. This stores the location and it always looks for XML files in this location. I have done this because normally an animator has a scene or scenes of characers to animate, and the lipsync XML files can be organised on the server into scenes so that the artist can set the working cache to the folder with their allocated scene at the start of the week and always get the correct files whenever they run.

SB_WCtt

If you hold the mouse over the button, you’ll be shown where the current cache directory is. If you hold SHIFT and click the button, windows will open an explorer dialog in the working cache location.

 

 

 

 

Loading/Saving to a location outside of the Working Cache Directory

With either, hold CONTROL down when pressing either load or save and you’ll be asked for a file if you want to load from another location.

Controlling key loading

Speechbot automatically saves all keys, from the entire animation range but has a few options so that you can control how the keys are loaded back onto the object.

Keep Existing Keys

By default, it clears all existing morph keys as normally you will want to replace any lipsync currently on the character, but if you want to avoid this behaviour, then hold down shift before you press load. This will keep the existing keydata. This is useful if you wanted to combine two files together, or load append some previous XML cache to the end of some other morph data.

Specify Load Range

SB_Str

Just as when it saves, Speechbot will restore all the keys over the entire range. However, if you just want to restore a specific time range, you can do so by clicking the Specify Load Time Range checkbox and typing a range in the spinners.

 

 

Use Offset

SB_uo

Occasionally, we would bump the audio forward in the curve editor to give us some more time at the start of a scene, so you can offset the key by a specific amount by checking use offset and providing a number.

Appendix – Interesting Scripting Factoids

I often wonder why these sections are called appendices. Is it because usually nobody ever bothers to read it, meaning that like the human appendix, doesn’t actually serve any purpose? (unless you count the abilty to digest grass as a good thing)

Anyway, so that I can give some scripting nuggets to anybody interested to read them, this is the place to find them.

Autonomous resource generation

It’s titles like that which put people off programming, but it’s sort of true. When you first run Speechbot, it contains a struct called LR_SpeechbotStruct. This part of the code checks if the the speechbot directory exists. If it doesnt, it assumes that this is the first time you have run the script and creates the directories and build the png image files that the dialog uses. I does this by way of constructing them from Base64 encoded strings. I wrote about this in a previous article but this is a practical example of how to use this method in a script so that you don’t have to provide any assets along with the code. If it detects that the directory is present, it assumes that the icons have been already created and doesn nothing, so it doesn’t interfere once the script has been run after the first time.

If you want to learn how to use XML to save pertinent data, take a look at the code. Since it is an XML based script, it seemed a bit silly to store dialog data to an ini file, so Speechbot uses an XML ini file. There is a method in the script for doing this so take a look and use it in your scripts too.

Download Speechbot V5

download speechbot