I was just reading a blog post by Nick Covington about retargeting missing bitmaps in 3ds Max scenes. He has a perfectly clever idea for doing so, but I wondered why I've never needed to write something similar for my users. Or rather, why so many people feel that bitmap retargeting is necessary?
3ds Max has an "External File Paths" feature found under Customize/Configure User Paths. It allows you to list folders in which bitmaps can be found on your system, regardless of where they were when first assigned. For instance, if you load a Max scene containing a reference to "C:\my_killer_art\foo.tga", but that file or folder doesn't exist on your PC, it will automatically look for "foo.tga" in each of the External File folders until it's found.
It also works for shader files. As long as you have paths in that list that contain all bitmaps your scenes could possibly use, you'll never see another "missing files" error again.
So am I missing something? Do people just prefer to retarget in the scenes themselves, rather than maintain that list of external file folders? I don't, but mileage might vary.
Admittedly, one limitation of that external file paths is that they don't automatically recurse into subdirectories. If you have a folder below one listed there, it will not look in that folder, it needs to be listed explicitly. Rather dumb.
Wednesday, February 20, 2013
Why retarget missing bitmaps in 3ds Max?
Monday, January 9, 2012
GDC 2012 Tech Artist Boot Camp Announcement
I'm organizing and MCing the Tech Artist Boot Camp at GDC 2012 in March. The TABC is an
all-day Tutorial-format session on Tuesday, March 6, from 10 AM-6
PM.
Below is the session description and list of speakers &
topics. We also plan to do a group panel-style Q&A session at the end of the
day.
I spoke at the TABC last year, and it was an excellent way to reach out to and share with other industry TAs. I hope to see you
there!
Description
Technical Art is evolving rapidly. In many
studios TAs play key roles in developing efficient tools pipelines and ensuring
art content is visually striking and optimized for performance. TAs bridge
content and engineering helping make both more successful. However, many studios
have still not fully embraced the TA role. Their TAs are smart and eager to make
an impact, but are not sure how to best prove their value, and be given key
roles in development.
A group of experienced, respected technical artists
from across the industry would like to invite you to sit with them for a day and
learn how to be a more effective TA. Focus on the tools and skills TAs can use
to demonstrate their value, and further integrate technical art into their
studios' pipelines and cultures. Find the worst development problems at your
studio and show them what a TA can do!
Intended Audience
This
all-day tutorial is for technical artists and other developers of any experience
level. A light focus will be placed on techniques and skills useful to TAs at
studios with little-to-no tech art integration and
culture.
Takeaway
At the end of this all-day event, attendees
will understand key techniques to help them take technical art to the next level
at their studios. Learn how to effectively work within constraints, integrate
into your teams, communicate with other disciplines, design better code and
pipelines, and master new shader techniques.
Speakers & Topics
---
Welcome, Introduction
Adam Pletcher, Technical Art
Director, Volition, Inc.
You Have to Start Somewhere... Defining
the Tech Art Role and Building Their Team
Arthur Shek, Technical Art
Director, Microsoft Studios (Turn 10)
This session will go over the
trials of moving from a job in film/animation to a studio with a minimal Tech
Art presence and the ensuing panic of change. The Tech Art role has a soft
definition and differs at every studio – our common quality is that we are
problem solvers, and to problem solve, you must have experience, wide knowledge
and the ability to scramble on your feet. At times, what we may feel pressure to
know can be overwhelming. Relax - you have to start somewhere.
Better,
Faster Stronger: Teaching Tech Artists to Build Technology
Rob
Galanakis, Lead Technical Artist, CCP Games
The success of Tech Art has
caused a complexity of projects and tools for which our traditional skill set is
under-equipped. Tech Artists are now building technology, not just scripts, and
our essential growth must be as a cohesive team, not just trained individuals.
In this session, attendees will learn how to apply a few key practices of
professional software development, such as code review, support processes, and
collaborative coding, to the unique environment of Tech Art.
Build it
on Stone: Best Practices for Developing A Tech Art Infrastructure
Seth
Gibson, Senior Technical Artist, Crystal Dynamics
In this session we
present a set of best practices for building Tech Art tools and pipelines in a
stable, maintainable, and scalable fashion through the establishment of a solid
tools development infrastructure geared toward the specific needs of Technical
Artists.
Joining the Dark Side: How Embedded Tech Artists Can Unite
Artists and Programmers
Ben Cloward, Senior Technical Artist, Bioware
Austin
Technical Artists can be a powerful force to unify teams and
ensure that productions run smoothly. In this case study, I’ll show how the
simple act of moving two technical artists into the programmers’ working area
helped to improve the relationship between art and programming and resulted in a
better-looking, more efficient game.
Lessons in Tool
Development
Jason Hayes, Technical Art Director, Volition,
Inc.
All too often, the importance of planning the architecture of tools
and pipelines in game development is overlooked. In most cases, project
pressures often give us the false impression that we don’t have time to plan, or
worse, we actually save time by “just getting it done”. Nothing could be further
from the truth. This session explains why up front planning is important, when
to recognize over-engineering and offers architectural design principles for
effective tools development-- such as program organization, data design,
scalability and user interface design. Internal tools developed at Volition will
be used to demonstrate these topics.
Shady Situations: Real-time
Rendering Tips & Techniques
Wes Grandmont III, Senior Technical
Art Director, Microsoft Studios (343 Industries)
This tutorial session
will cover a variety of techniques that can be used individually or combined to
solve a variety of game related real-time shading problems. It will begin with a
brief overview of the current generation GPU pipeline, followed by some HLSL
basics. The rest of the talk will dive into a range of techniques with a
complete overview of how each one is implemented.
Unusual UVs:
Illuminating Night Windows in Saints Row The Third
Will Smith,
Technical Artist, Volition, Inc.
This session presents a holistic case
study involving HLSL shader development. Included is not only the problem and
its resolution, but perhaps more importantly, an insight into the Technical
Artist’s problem-solving mindset throughout its resolution.
Group
Q&A, Conclusion
Sunday, December 6, 2009
MaxScript DotNet Sockets with Python
I create and work with several Python tools that manipulate scenes in 3ds Max. These are usually floating dialogs linked to the main 3ds Max window that send MaxScript commands via COM. This works well until I need the tool's UI to update when something happens in 3ds Max. Like refresh an object list when the selection in Max changes.
You would think doing a COM connection the other way would work. However, since Python-registered COM servers run separately in their own instance of the interpreter, there's no native connection to the original tool.
Nathaniel Albright, a fellow TA at Volition, recently created a Python COM server that communicated to his Python tool via TCP/IP socket. So it went 3ds Max -> Python COM server -> TCP/IP -> Python tool. This works well, but I wondered if the DotNet facilities in MaxScript offered a direct way to use sockets.
I had yet to touch DotNet in MaxScript, so this seemed like a good opportunity to learn a few things. After a lot of searching online I only turned up a few scraps of info, no complete recipe. However, I did find enough to get MXS DotNet sockets working, and assemble a comprehensive example.
I created a little Python tool that displays the names of all selected objects in the 3ds Max scene. As the scene selection changes, the list of names automatically updates. I won't go over all the code in this post, but the full working example tool is included in the zipfile below.
There's three main points of interest in the example:
1. Using DotNet in MaxScript to communicate via TCP/IP socket
2. Listening on a socket in a background thread in Python
3. Creating and posting custom wxPython events
The MaxScript Client
I made a MaxScript struct called "mxs_socket". The code follows, and also included in the zipfile below.
struct mxs_socket ( ip_address = "127.0.0.1", -- "localhost" also valid port = 2323, -- default port -- <dotnet>connect <string>ip_string <int>port_int -- -- Description: -- Takes IP address, port and connects to socket listener at that -- address fn connect ip_string port_int = ( socket = dotNetObject "System.Net.Sockets.Socket" ( dotnetclass "System.Net.Sockets.AddressFamily" ).InterNetwork ( dotnetclass "System.Net.Sockets.SocketType" ).Stream ( dotnetclass "System.Net.Sockets.ProtocolType" ).Tcp socket.Connect ip_string port_int socket -- return ), -- <int>send <string>data -- -- Description: -- Converts a string (or any object that can be converted -- to a string) to dotnet ASCII-encoded byte sequence and -- sends it via socket. Uses ip_address and port defined -- in struct above, or set by client. -- Returns integer of how many bytes were sent. fn send data = ( -- Convert string to bytes ascii_encoder = dotNetObject "System.Text.ASCIIEncoding" bytes = ascii_encoder.GetBytes ( data as string ) -- Connect, send bytes, then close socket = connect ip_address port -- result is # of bytes sent result = socket.Send bytes socket.Close() result -- return # of bytes sent ) )Using this, I can send bytes to any socket listener on port 5432 by doing the following:
socket = mxs_socket port:5432 socket.send "Hello, World!"The connect method was pretty simple in the end. The only twist turned out to be converting the socket integer into a DotNet socket object.
The send method converts the string into an ASCII-encoded DotNet bytes object, connects to the listener, sends the bytes, then closes the connection. The value returned is the number of bytes sent.
The last lines of the above code sets up a MaxScript callback that fires when the object selection changes in the scene. That uses mxs_socket to send a string containing the names of all the selected objects to any tool that's listening on that port.
Now I just need to make my Python tool listen.
The Python Server
My Python server/listener (also in the zipfile below) is a typical wxPython frame, but with two added qualities... It uses a background thread to listen on a socket, and posts a custom wx.Event when data is received. I had never used either of these techniques before, but it was fun getting it working.
Since a typical wxPython app sits in its main loop waiting for user input, I created a Socket_Listen_Thread class, a subclass of threading.Thread. This does the listening in a background thread while the main UI thread waits on the user. The run method here does the real work:
def run( self ): self.running = True while ( self.running ): # Starting server... # Listen for connection. We're in non-blocking mode so it can # check for the signal to shut down from the main thread. try: client_socket, clientaddr = self.socket.accept( ) data_received = True except socket.error: data_received = False if ( data_received ): # Set new client socket to block. Otherwise it will # inherit the non-blocking mode of the server socket. client_socket.setblocking( True ) # Connection found, read its data then close data = client_socket.recv( self.buffer_size ) client_socket.close( ) # Create wx event and post it to our app window event = self.event_class( data = data ) wx.PostEvent( self.window, event )This listening thread runs quietly in the background until it receives data. At that point I need a way to break into the main UI thread. There's other ways to do this, but using a custom wx.Event seemed to be the best fit here.
First, when the wx.Frame is opened, I create a custom wxPython event.
(Max_Update_Event, EVT_3DS_MAX_UPDATE) = wx.lib.newevent.NewEvent()Calling NewEvent returns both a new Event class and an object to bind the event handler to. I pass the event class to the listener thread, bind an event handler to it, and that's all.
When data comes in over that TCP/IP port from our MaxScript tool, the listening thread receives it and posts our custom event to the main wx.Frame. That in turn fires the event handler to update the UI.
My example MaxScript client and Python listener described above can be found in the following ZIP file. Drop me a line if you do something useful with them, I'd love to hear about it.
MaxScript_DotNet_Sockets_Python.zip (4 KB)
Thanks to Nate Albright and everyone contributing to the "dotNet + MXS" and "Python + MXS" threads on the CGSociety forums. Those long-running threads have been very inspiring, and contain several tips that were key in getting the MaxScript DotNet socket stuff hammered out.
Saturday, June 27, 2009
Hidden HiddenDOSCommand details
Twice in recent months I've been bitten by MaxScript's HiddenDOSCommand.
It was added in 3ds Max 2008 as a way to issue DOS commands without bringing up an ugly command prompt. Sounds great but what the docs don't tell you is that the optional "startpath:
HiddenDOSCommand "notepad %temp%\\cmdout.tmp" prompt:"Waiting..." -- Error! CreateProcess(cmd /e:on /d /c "notepad %temp%\hiddencmdout.tmp") failed!Note, that command was pasted from Example Usage in the MaxScript docs for HiddenDOSCommand. It will not work, nor will the other examples listed there unless you include "startpath"....
HiddenDOSCommand "notepad %temp%\\cmdout.tmp" prompt:"Waiting..." startpath:"C:\\" trueThis may have been addressed in the helpfile for 3ds Max 2010, I haven't checked. This can be Google fodder in the meantime.
Sunday, May 10, 2009
What we do with Python
There's a great thread going at tech-artists.org called What do you do with Python? The other day I posted a few of the things our studio has done with Python in the past year or two...
- Measure start/stop times of various processes, logging data to SQL database. For instance, how long it takes 3ds Max to start up, so we can spot bad trends when new tools are published.
- System for logging errors and tools usage data to central database, with optional emailing of errors/callstack. Works for Python tools as well as MaxScript (via COM).
- A non-linear GUI editor for an otherwise complex/table-driven cutscene pipeline.
- Build graphical user interfaces (generally with wxPython) that integrate with in-house and off-the-shelf C applications. For example, floating Python dialogs that link to app windows as children, or as docking task panes.
- Tool that communicates with game C code (via socket) running on consoles to do in-game realtime lighting.
- Embed Python interpreter into editor framework for next-gen development tools. This is the one I spend lots of time on these days... works like MaxScript in 3ds Max, but for our custom editors.
- One Exporter that writes out various data files from 3ds Max, Photoshop, and imports/categorizes them in our asset system.
- Logs me into Outlook's webmail without manually entering my creds every time. I guess that was a home project. :)
- At 3ds Max startup, scan folders for MaxScripts, building a MacroScript .mcr file for all of them.
- At 3ds Max startup, builds list of texture map folders for a given project, sorts them by user's discipline and adds them to Max's bitmap paths list.
- Profile rendering performance of art assets recently submitted to Perforce, recording data to SQL database.
- Searches web-based bug tracker database for entries assigned to you and displays data in a Vista Sidebar gadget.
- Creates makefiles with dependencies, for distributed build processes in Incredibuild/XGE.
- Wavelet transform calculations for content-based image comparison tools. For finding textures that are too similar, or comparing rendered output of one shader vs. another.
- Takes zipcode or lat/long as input, gathers geo-survey data from various online sources and creates the road/terrain network inside our world editor.
- Tons of data mining uses. Like searching various exported XML files for instances of X material, mesh, etc. in game world.
- Tool for bridging various apps with COM interfaces in other tools. Like firing MaxScripts in 3ds Max from Ultraedit, or taking current Python script in Wing and running it in our editor's embedded interpreter.
- Custom scripts for integrating our tools/processes into Wing (the Python IDE we use).
P.S. Call your mom today.
Friday, August 22, 2008
ColladaMax for 3ds Max 2009 64-bit
I was unable to find the ColladaMax importer/exporter plugin for 3ds Max 2009 64-bit, so I built one. It's from the 3.05b source.
Update 02/03/09 - I made a new build that depends on an older version of the DirectX SDK. It should fix the "failed to initialize" error some of you were getting, without the need to install anything else.
Feel free to grab it if you like:
ColladaMax_2009x64.rar (709 KB)
Wednesday, July 16, 2008
Checksums in 3ds Max (part 2 of 2)
In Part 1 I showed how to calculate checksums inside 3ds Max. Here's how to do something useful with them.
Any TA that's crossed paths with 3ds Max can tell you it doesn't do the best job of managing scene materials. Due to scene object merges/imports and other typical operations, it's common for a given material to be copied several times in one Max scene. Meaning, it's not instanced across several objects, but actually copied several times in memory. This can lead to increased memory usage and potentially inefficiencies in your game engine (depending how your exporter deals with this).
What's worse, you usually can't rely on similar material names to find duplicates by hand. To do a thorough search with MaxScript, you would need to loop through every material in the scene and compare every property in it to every other material's property. This would be a slow process in C, and a complete horror-show with MaxScript.
Enough grim talk. Here's a walkthrough of a MaxScript that uses checksums to make short work of this. To summarize, the script loops through the materials in your scene, creating a checksum for each as it goes. It uses that checksum to do a quick compare on previous material checksums it found, to see if they're actually property-identical. If it finds a dupe, that object is given the original material instead, effectively deleting the duplicated material.
The script is divided into three functions and a short bit of main code.
getChecksum() is the first function, taken from my previous post. It calls the Python COM object we registered, which returns a checksum to the MaxScript. If you can't (or don't want to) set-up the COM object, you can use the MaxScript implementation I listed in that blog post instead... it's just less robust than the MD5 checksums used by the Python method.
Next is the getPropsString() and getMaterialChecksum() functions:
------------------------------------------------------------ -- (str)getPropsString (material)mat -- -- Description: -- Builds a string representing the property names/values -- of the supplied Max material. ------------------------------------------------------------ fn getPropsString mat = ( myStr = "" as stringStream if (mat == undefined) then ( format "undefined" to:myStr ) else ( -- Start our string w/the classname format (classOf mat as string) to:myStr if (classof mat == ArrayParameter) then ( -- Array, so recursively add strings for each element for element in mat do ( format (getPropsString element) to:myStr ) ) else ( -- Not an array, so see if it has properties propNames = undefined try ( propNames = getPropNames mat ) catch () if (classOf mat == BitMap) then ( try ( -- Add bitmap's filename format mat.filename to:myStr ) catch () ) else if (propNames == undefined) then ( format (mat as string) to:myStr ) else ( format (propNames as string) to:myStr -- Loop through properties, adding their names -- and values to our string to be checksummed for i in 1 to propNames.count do ( format (i as string) to:myStr p = propNames[i] val = getProperty mat p format (i as string) to:myStr format (getPropsString val) to:myStr ) ) ) ) (myStr as string) ) ------------------------------------------------------------ -- (str)getMaterialChecksum (material)mat -- -- Description: -- Takes a Max material (or multi-sub material) and -- calculates a checksum value from it, for use as a -- hashtable key, or whatever you like. ------------------------------------------------------------ fn getMaterialChecksum mat = ( str = "" if (classof mat == Multimaterial) then ( for id in mat.materialIDList do ( -- Add material IDs as factors str += id as string ) for subMat in mat.materialList do ( -- Get string representing each submaterial str += (getPropsString subMat) ) ) else ( -- Get string representing this material str += getPropsString mat ) -- Add string length as a factor str += str.count as string -- Get checksum from our base string -- 99991 = largest prime number under 10k (getChecksum str) )The above functions work together to generate a string of data representing the supplied 3ds Max material (or Multi-Sub material). Once it has that string, it's passed to getChecksum().
The main code block loops through the entire 3ds Max scene, doing the above for every material found on geometry objects:
---------- -- MAIN ---------- -- Set up a few things first. timeStart = timestamp() -- Time we started process removedCount = 0 -- Counters for printing info below uniqueCount = 0 -- Array of two synced arrays, first with the material -- checksums, second with materials themselves. -- Basically a poor-man's hashtable. csMatArr = #(#(), #()) format "Scanning scene materials...\n" -- Loop through all geometry for obj in geometry do ( mat = obj.material alreadyDone = (findItem csMatArr[2] mat) != 0 if (not alreadyDone) and (mat != undefined) then ( -- First get this material's checksum csum = getMaterialChecksum mat idx = findItem csMatArr[1] csum if (idx != 0) then ( -- Dupe material found, so remove it by -- assigning the first mat to this object format "Replacing material '%' with '%'\n" mat.name csMatArr[2][idx].name obj.material = csMatArr[2][idx] removedCount += 1 ) else ( -- New checksum, so add it to our table, -- along with the material itself. append csMatArr[1] csum append csMatArr[2] mat uniqueCount += 1 ) ) ) gc() -- Remind Max to take out the trash -- Done, print some results format "-- DONE in % secs --\n" ((timestamp() - timeStart) / 1000.0) format "Old material count = %\n" (uniqueCount + removedCount) format "New material count = %\n" uniqueCount format "Duplicates removed = %\n" removedCountThat's it. At the end a summary is printed to the MaxScript Listener.
In the Max scene I was working with today this script cut the root material count from 533 to 261. That's 51% fewer materials! It also reduced the file load time from 136 seconds to 102 seconds.
You can download the complete script above here: RemoveDupeMaterials.zip (4 KB)
It includes the Python script to register the COM server, and the alternate MaxScript checksum method.
Update 7/25/08: I modified getPropsString() to better handle bitmap values, and generally run faster. The ZIP file above is updated as well. Thanks to MoonDoggie/Colin on CGTalk for the feedback!
Wednesday, June 18, 2008
Checksums in 3ds Max (part 1 of 2)
In my Calling Python from MaxScript post I mentioned the usefulness of checksums in Tech Art work. I was hoping to elaborate on that a bit.
In short, a checksum is a number computed from a larger piece of data. The checksum is (ideally) guaranteed to be unique for that data. Let's say that data is this string: "Tech Art is A-#1 Supar", and the checksum you've computed is "30532". If any character in that string changes, the computed checksum for it will be different, like "18835" or "1335"... basically anything other than "30532".
Checksums are most useful in cases where you need to compare two sets of data to see if they differ, but don't care where or how they differ. If you have a short number that uniquely identifies a huge piece of data, you can compare it to other data sets much faster/easier than comparing every element of the original data. If you're hip to how slow n-squared searches can be (especially in languages like MEL or MaxScript), this is a classic method for avoiding them.
Here's a MaxScript function that takes a string of any length and returns a checksum for it.
------------------------------------------------------------ -- (str)getChecksum (string)val (int)size:256 -- -- Description: -- Calculates simple checksum value from supplied string (or -- any value convertable to a string). Default size is 256, -- but can be changed with the optional "size" parameter. ------------------------------------------------------------ fn getChecksum val size:256 = ( if (classof val != String) then ( try ( val = val as String ) catch ( return false ) ) alphaKey = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 !@#$%^&*()[]\\{};':\",./<>?" total = 0 for i in 1 to val.count do ( thisVal = findString alphaKey val[i] if (thisVal == undefined) then ( thisVal = 0 ) -- Multiply the alphanumeric value by its position in -- the input string, add to running total total += (thisVal * i) ) -- make sure divisor is smaller than dividend while (total < size) do ( total = total * 2 ) -- Return final checksum value checksum = mod total size return (checksum as string) )We used the above function in several of the Saints Row tools, primarily to help remove identical materials in 3ds Max scenes. It's very unscientific, however, and can generate collisions in rare cases (two different input strings that generate the same output checksum) **.
If you don't mind a little more setup, I would recommend an alternate checksum method. Python natively offers more robust checksum tools, and can be set up to be called directly from MaxScript. The steps for doing this, and the actual MD5 checksum function, are covered in my earlier blog, Calling Python from MaxScript.
Start with the Python script from that blog that defines and registers the COM server. Then you're able to use a far-shorter MaxScript function to get checksums:
fn getChecksum val = ( comObj = createOLEObject "PythonCom.Utilities" checksum = comObj.checksumMD5 val return checksum )That's it. The checksums you get from this function will create fewer collisions than the pure-MaxScript one above, and can be made to use any alternative method available in Python.
Now you know more about checksums, and how to generate them in 3ds Max. Next time (in Part 2) I'll discuss how you can use them to save memory in both Max and your game engine.
Tuesday, May 6, 2008
Calling Python from MaxScript
Unlike Maya, 3ds Max does not have internal support for Python. But that shouldn't stop you from calling useful Python code in your MaxScripts! Here's the basics of how to do that using COM.
COM is a Windows system that supports, among other arcane things, interprocess communication. You can use a language like Python, Visual Basic, or C to define a COM "server". This is a class or function, defined by a unique identifier (GUID) and a name. Here's some gory details on COM if you're curious.
Here's a simple COM server using Python:
Requires the Python Win32 Extensions (which no TA should be without)
# A simple Python COM server. class PythonComUtilities: # These tell win32 what/how to register with COM _public_methods_ = ['checksumMD5'] _reg_progid_ = 'PythonCom.Utilities' # Class ID must be new/unique for every server you create _reg_clsid_ = '{48dd4b8f-f35e-11dc-a4fd-0013029ef248}' def checksumMD5(self, string): """Creates MD5 checksum from string""" import hashlib m = hashlib.md5() m.update(str(string)) return m.hexdigest() if (__name__ == '__main__'): print 'Registering COM server...' import win32com.server.register as comReg comReg.UseCommandLine(PythonComUtilities)This defines a function,
checksumMD5
that takes a string as input, and returns the MD5 checksum for that string.To register the COM server on a PC, simply run the Python script. Windows records it in registry, noting which script/application it uses.
Now that's done, another application (3ds Max, in this case) can connect to that COM server's interface and call it like any other function. Here's an example of doing that from MaxScript:
-- Connect to the COM server by name comObj = createOLEObject "PythonCom.Utilities" -- Call the function it exposes, with a sample string checksum = comObj.checksumMD5 "The quick brown fox."It's that simple. The checkum value returned for our sample string is
"2e87284d245c2aae1c74fa4c50a74c77"
.You might be wondering what a checksum is, or what it's good for. Stay tuned and I'll show you some slick stuff you can do with them in 3ds Max. See Checksums in 3ds Max, Part 1 and Part 2.
Python COM server example adapted from code appearing in Python Programming in Win32 by Mark Hammond and Andy Robinson... a great book for getting more out of Windows with Python.