Browsing"3dsMax"

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

 

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

Adding mirror functionality to a custom rig

Jan 18, 2011 by     No Comments    Posted under: 3dsMax, Characters, DotNet, Maxscript, Rigging, User Controls

On our last production, I had to find a quick way of implementing a system that would allow me to set up basic mirror functions on any custom rig. Specifically, I needed a toolset that would standardise the main functions of the rig like selection tools for the current side of the body, mirror select, copy, paste and IK/FK switching.

I accomplished this with a simple custom attribute that allowed me to store the nodes of each half of the body as weak references. If you don’t already know, weak references are the golden goose of character rigging. They allow you to store arbitrary nodes within the character so that you can retrieve them at any stage without having to select the node itself.

RigGUI

In the video below, you can see how I set up a full mirror/rig selection system on a puppetshop rig. Puppetshop is great because it already has a load of good stuff already built in, so the GUI I add in the video (which is a custom dotnet interface) can simply expose functions from the puppetshop API.

The selector always knows what the opposite side of the rig is because you store each node’s opposite in the parameter block of the attribute.

So what it does is allows me to set up this feature on any rig within a minute or so. Because you can store the node assignments to XML, it is easy to mirror the nodes onto the other side of the rig for even faster setup.

I hope this video gives some insight into how automated setups can save a wealth of time in production.

If you want to try a max script that uses weak references on a rig, please read my previous post –

http://lonerobot.net/?p=158

I talk about them in a bit more detail and show you how you can control rig visibility using them.

Using HitchHiker – Building a Matte/Shadow material utility

Dec 21, 2010 by     2 Comments    Posted under: 3dsMax, DotNet, Imaging, Maxscript

This post is to show how you can use Hitchhiker to develop maxscript tools in super quick time. Any auto thumbnailer is going to have about a million uses in production in my humble opinion, as most of the development time on a utility is spend building the UI functionality. Whilst on the subject of dotnet controls, I would like to draw attention to another dotnet thumbnail control out there, written by TD Jason Labbe. It’s got a few things that Hitchhiker doesn’t have like multi select, so might be useful if Hitchhiker hasn’t quite got the functionality you need. Nice job Jason! You can read about it here

The Problem

On our last production, we had to consolidate a lot of complex sets into background renders as we were on tight deadlines. Often, the shot would need some form of contact shadow as well as masking so the obvious choice was to use the mental ray matte material. Depending on the type of shot, HD render times could be cut from 10 minutes per frame to around 45 seconds, with no perceptible loss of detail or quality.

The Solution

I wrote a utility that allowed the user to browse for the rendered backgrounds and quickly set the material up as a matte material, ready to be assigned to the background objects and rendered.

MatteHelper

This utility is a good example of what you can use HitchHiker for. The browsing window allows you to pick the map that you want, and with the new cache mode property on the latest hitchhiker, is very fast. The greatest thing was that I wrote this entire script on my train ride home one day. This was exactly why I wrote HitchHiker, a plug and play solution to speed up my deployment of production scripts.

The material setup is simple,  matte/shadow.reflection (mi) shader is made with an Environment/Background Map (mi) shader. The texturemap is then set to screen and the blur taken down. You have to option to include the alpha so that you can use the render for composition. While the script itself is simple, it’s an example of how you can speed up a common bottleneck with very small programming outlay. Suffice to say, it checks that Mental Ray is the production renderer, as the materials are only compatible with that, but if you wanted to adjust it for use with an alternative material setup, it would be very easy. There is a method called :

CreateMatteShader <texmap> <slotnumber>

This could be changed to whatever you liked.

There is another assembly that is included with this script too, called ImageBot. It is an extended picturebox that allows you to zoom in on an area by dragging a marquee with the the right mouse button. This could also be used in other scripts, for example a rendered frame buffer.

How to use the script

Make a copy of your scene and render all static elements and save the rendered background file. Start MonsterMatte and use HitchHiker to navigate to the rendered file and select it. Press ‘Create Shader’. The matte material will be created and added to the designated material slot.

Apply the material to anything in the scene that isn’t animated and render – watch how fast it goes!

Installation

To install, just download the script below and copy the folders to the root of your max installation. You’ll see the macroscript listed under the ‘LoneRobot’ category and is called MonsterMatte. It’s not really a monster tool, but unfortunately when I was writing this article that monster mash song kept going round my head, damn pesky irritating brain.

Merry Xmas!

download

Something sparkling for Christmas…

Dec 18, 2010 by     No Comments    Posted under: 3dsMax, Tips and Tricks

Jamie’s Jewels is a blog is created by Jamie Gwilliam of Autodesk. Jamie has been a very useful contact at Autodesk for me on the last project. He arranged a meeting for us with the max design team and we had a great chat about the software and the direction it was heading in the future.

As an application specialist for Autodesk he really knows his stuff. He helped us with his imaging expertise and contacts to get our monitors properly calibrated.

The blog concentrates on 3ds Max/Design and Design Visualisation. This covers past email newsletters as an archive and also includes articles he has produced on image creation.

Check it out here –

http://bit.ly/3jamiesjewels