Notifications
Clear all

[Closed] Materials to XML

Here’s a bit of code I’ve been working on to save materials to XML format.
Since I’ve started working on MatManager I’ve been feeling lees and lees impressed with the .mat format, and since I’d like to be able to save my own data along with the materials I figured why not create my own format.

Normally MAX allows you to save materials to a .mat file, however this format is not backwards compatible, thus making it impossible to (for example) to open a file saved in MAX 2009 in MAX 9. This script makes it possible to circumvent that “limitation”…

Since this has been common complaint amongst MAX users I figured someone might benefit from the code. I’ll add an importer a maybe a GUI once I have some time.


 /*********************
MatManager XML Exporter
**********************/
--By Marco Brunetta
--UNSUPPORTED: Curve Control // Color Gradient // SubMaterials
--ADD: Multi/Sub Material support & Blend material support
--ADD: keep undefinedClass from saving data
--ADD: check if the propertyElement has subProperties, if it does do not assign an inner text (should be able to remove  a couple fo the classes when writing the XML using this)
(
	dotNet.loadAssembly "system.xml" --Loading of XML assembly
	
	local theMat = (getMeditMaterial 1) --The Material to save
	local theSavePath = ("D:\\TEMP\\"+theMat.name+".mmmat") --The path where the XML will be saved, chenge ".mmm" to ".txt" if you want to check the contents
	
	STRUCT propertyElement --Object used for gathering material info
	(
		name,
		type,
		value,
		subProperties
	)
	
	fn collectProperties theObject = --Function that gathers the material properties (techinically it could be used for any object I guess)
	(
		local collectedProperties =#()
		local thePropNames = (getPropNames theObject)
		FOR propName in thePropNames DO
		(
			local propertyValue = getProperty theObject propName
			local thePropElement = propertyElement name:propName type:(classOf propertyValue) value:propertyValue subProperties:#()
			IF (TRY (getPropNames thePropElement.value).count CATCH(0)) > 0 DO
			(
				thePropElement.subProperties = collectProperties thePropElement.value
			)
			IF thePropElement.type == ArrayParameter DO
			(
				local oldProperties = thePropElement.value
				thePropElement.value = #()
				FOR oldProp in oldProperties DO
				(
					local arrayPropelement = propertyElement name:"arrayItem" type:(classOf oldProp) value:oldProp subProperties:#()
					IF (TRY (getPropNames oldProp).count CATCH(0)) > 0 DO
					(
						arrayPropelement.subProperties = collectProperties arrayPropelement.value
					)
					append thePropElement.value arrayPropelement
				)
			)
			append collectedProperties thePropElement
		)
		collectedProperties
	)

	local theProperties = collectProperties theMat
	local xmlDoc = dotNetObject "system.xml.xmlDocument" 
	local theRoot = xmlDoc.createElement "MatManagerMaterial" 
	theRoot.setAttribute "type" ((classOf theMat) as string)
	xmlDoc.appendChild theRoot
	
	fn createPropertyNode propElement = --functions that writes the properties into XML format
	(
		local XMLNode = xmlDoc.createElement (propElement.name as string)
		XMLNode.setAttribute "type" (propElement.type as string)
		CASE propElement.type OF
		(
			default:
			(
				IF classOf propElement.type == textureMap THEN --saving of textureMaps
				(
					local theNameNode = xmlDoc.createElement "name"
					theNameNode.innerText = propElement.value.name
					theNameNode.setAttribute "value" "String" 
					XMLNode.appendChild theNameNode
					FOR subProp in propElement.subProperties DO 
					(
						local newSubProp =	createPropertyNode subProp 
						XMLNode.appendChild newSubProp
					)
				)
				ELSE --saving of normal stuff (blooean, integer, float, etc)
				(
					XMLNode.innerText = (propElement.value as string)
					FOR subProp in propElement.subProperties DO 
					(
						local newSubProp =	createPropertyNode subProp 
						XMLNode.appendChild newSubProp
					)
				)
			)
			StandardUVGen: --saving of 2d coordenates
			(
				FOR subProp in propElement.subProperties DO 
				(
					local newSubProp =	createPropertyNode subProp 
					XMLNode.appendChild newSubProp
				)
			)
			StandardXYZGen: --saving of 3d coordenates
			(
				FOR subProp in propElement.subProperties DO 
				(
					local newSubProp =	createPropertyNode subProp 
					XMLNode.appendChild newSubProp
				)
			)
			StandardTextureOutput:  --saving of texture output
			(
				FOR subProp in propElement.subProperties DO 
				(
					local newSubProp =	createPropertyNode subProp 
					XMLNode.appendChild newSubProp
				)
			)
			Point2: --saving of point2D
			(
				local xProp = xmlDoc.createElement "X" 
				xProp.innerText = (propElement.value[1]) as string
				XMLNode.appendChild xProp
				local yProp = xmlDoc.createElement "Y"
				yProp.innerText = (propElement.value[2]) as string
				XMLNode.appendChild yProp
			)
			Point3: --saving of point3d
			(
				local xProp = xmlDoc.createElement "X" 
				xProp.innerText = (propElement.value[1]) as string
				XMLNode.appendChild xProp
				local yProp = xmlDoc.createElement "Y"
				yProp.innerText = (propElement.value[2]) as string
				XMLNode.appendChild yProp
				local zProp = xmlDoc.createElement "Z"
				zProp.innerText = (propElement.value[3]) as string
				XMLNode.appendChild zProp
			)
			Matrix3: --saving of matrix3
			(
				local matrixA = xmlDoc.createElement "A"
				local aXProp = xmlDoc.createElement "X" 
				aXProp.innerText = (propElement.value[1][1]) as string
				matrixA.appendChild aXProp
				local aYProp = xmlDoc.createElement "Y"
				aYProp.innerText = (propElement.value[1][2]) as string
				matrixA.appendChild aYProp
				local aZProp = xmlDoc.createElement "Z"
				aZProp.innerText = (propElement.value[1][3]) as string
				matrixA.appendChild aZProp
				XMLNode.appendChild matrixA
				
				local matrixB = xmlDoc.createElement "B"
				local bXProp = xmlDoc.createElement "X" 
				bXProp.innerText = (propElement.value[2][1]) as string
				matrixB.appendChild bXProp
				local bYProp = xmlDoc.createElement "Y"
				bYProp.innerText = (propElement.value[2][2]) as string
				matrixB.appendChild bYProp
				local bZProp = xmlDoc.createElement "Z"
				bZProp.innerText = (propElement.value[2][3]) as string
				matrixB.appendChild bZProp
				XMLNode.appendChild matrixB
				
				local matrixC = xmlDoc.createElement "C"
				local cXProp = xmlDoc.createElement "X" 
				cXProp.innerText = (propElement.value[3][1]) as string
				matrixC.appendChild cXProp
				local cYProp = xmlDoc.createElement "Y"
				cYProp.innerText = (propElement.value[3][2]) as string
				matrixC.appendChild cYProp
				local cZProp = xmlDoc.createElement "Z"
				cZProp.innerText = (propElement.value[3][3]) as string
				matrixC.appendChild cZProp
				XMLNode.appendChild matrixC
				
				local matrixD = xmlDoc.createElement "D"
				local dXProp = xmlDoc.createElement "X" 
				dXProp.innerText = (propElement.value[4][1]) as string
				matrixD.appendChild dXProp
				local dYProp = xmlDoc.createElement "Y"
				dYProp.innerText = (propElement.value[4][2]) as string
				matrixD.appendChild dYProp
				local dZProp = xmlDoc.createElement "Z"
				dZProp.innerText = (propElement.value[4][3]) as string
				matrixD.appendChild dZProp
				XMLNode.appendChild matrixD
			)
			Color: --saving of colors
			(
				local newRProp = xmlDoc.createElement "Red" 
				newRProp.innerText = propElement.value.r as string
				XMLNode.appendChild newRProp
				local newGProp = xmlDoc.createElement "Green"
				newGProp.innerText = propElement.value.g as string
				XMLNode.appendChild newGProp
				local newBProp = xmlDoc.createElement "Blue"
				newBProp.innerText = propElement.value.b as string
				XMLNode.appendChild newBProp
				local newAProp = xmlDoc.createElement "Alpha"
				newAProp.innerText = propElement.value.a as string
				XMLNode.appendChild newAProp
			)
			ArrayParameter: --saving of Arrays
			(
				FOR arrayProp in propElement.value DO 
				(
					local newArrayProp = createPropertyNode arrayProp 
					XMLNode.appendChild newArrayProp
				)
			)
		)
		return XMLNode
	)
	
	local theNameNode = xmlDoc.createElement "name" --create name node
	theNameNode.innerText = theMat.name
	theNameNode.setAttribute "value" "String" 
	theRoot.appendChild theNameNode
	
	FOR propElement in theProperties DO --save the material properties
	(
		local newNode = createPropertyNode propElement 
		theRoot.appendChild (xmlDoc.importNode newNode true)
	)
	
	xmlDoc.save theSavePath --save the XML file
)
 

While not everything is 100% supported, the script should be able to save any material type with any number of maps. Support for submaterials has not been added yet.

Opinions welcomed.

4 Replies

seems like a more code-agnostic form of bobo’s file format, in a way, at least as to its purpose.

we coded something similar to BFF for materials and unfortunately one of the major hurdles in this is that simply not everything -can- be accessed (read and/or write) from maxscript. it’s still great for all the basic materials, but add in something common as a gradient ramp, or a mix map with output curve tweaking, and there goes the connection

Code-agnostic? What delightfully convoluted assessment of my mad coding skillz :p. Yhea I know, some parts are bit too “hardcodedlish” and some are just darn fugly. It was just a day’s work mate cut me some slack.

Anyways, yes I noticed the limitations…however being able to share 80% of the material with older versions of max is still better than no sharing at all.

Hey Marco, have you checked this out? http://forums.cgsociety.org/showthread.php?f=98&t=618821&highlight=material+xml

Maybe it can help you out and save you some time coding.

Cheers and good work.

I actually had a quick look after I posted this… might have been a good idea to do it before.

I don’t normally use Mental Ray materials though, so it’s better for me to have something more “generic”.