Yes, the majority of Thinkbox Software’s 3ds Max plugins use Function Publishing. In fact, they use our own FP system that is easier to use (by us internally) than the one Autodesk provides, but that does not affect the end user. In addition, for most products, we made the decision to split the core functionality and UI code right in the middle and develop all UI in MAXScript, communicating with the core plugin via the published MAXScrcipt Functions and Interfaces.
XMesh Saver is one of those plugins whose UIs are completely written in MAXScript, and unprotected (not in an MSE). Opening the XMesh Saver really just involves the launch of its MacroScript, e.g.
Macros.Run "Thinkbox" "XMeshSaver"
You can take a look at the complete UI code, as well as some utility scripts by going to the folder
C:\Program Files\Thinkbox\XMeshSaver MX\Scripts\
In fact, we encourage studios to write their own implementations of the XMesh Saver UI that fit their pipeline - the UI we ship is a One Size Fits All and sometimes a bit too much Back in the days when we were still a production studio and XMesh was just an internal tool, I wrote at least 3 alternative UIs for the artists to expose only what they needed…
The complete core functionality of XMesh Saver is exposed via a single MAXScript FP Interface called XMeshSaverUtils:
showInterface XMeshSaverUtils
Interface: XMeshSaverUtils
Properties:
.HasLicense : bool : Read
.Version : string : Read
.VersionNumber : string : Read
.XMeshSaverHome : string : Read
.SettingsDirectory : string : Read
.TimeStepInitialOffset : float : Read|Write
.TimeStepScale : float : Read|Write
.DefaultMaterialID : integer : Read|Write
.CompressionLevel : integer : Read|Write
.ThreadCount : integer : Read|Write
.PopupLogWindowOnError : bool : Read|Write
.LogWindowVisible : bool : Read|Write
.LoggingLevel : integer : Read|Write
.SourceChannels : string by value array : Read|Write
.ObjFlipYZ : bool : Read|Write
Methods:
<void>SetSequenceName <string>SequenceName
<void>SaveMeshToSequence <node>Node <bool>IgnoreEmptyMeshes <bool>IgnoreTopologyChanges <bool>UseObjectSpace <bool>FindVelocity
<void>SaveMeshesToSequence <node array>Nodes <bool>IgnoreEmptyMeshes <bool>IgnoreTopologyChanges <bool>FindVelocity
<void>SavePolymesh <node>Node <string>Path <bool>VertsOnly WorldSpace:<bool>
WorldSpace default value: false
<void>SavePolymeshToSequence <node>Node <bool>IgnoreEmptyMeshes <bool>IgnoreTopologyChanges <bool>UseObjectSpace <bool>FindVelocity
<void>SavePolymeshesToSequence <node array>Nodes <bool>IgnoreEmptyMeshes <bool>IgnoreTopologyChanges <bool>FindVelocity
<void>SetSceneRenderBegin()
<void>SetSceneRenderEnd()
<void>ConfigureLicense()
<void>AcquireLicense()
<void>ReleaseLicense()
<void>ClearAllMaterialIDMapping()
<void>SetMaterialIDMapping <node>MeshNode <int array>FromMaterialIDList <int array>ToMaterialIDList
<void>LogError <string>Msg
<void>LogWarning <string>Msg
<void>LogProgress <string>Msg
<void>LogStats <string>Msg
<void>LogDebug <string>Msg
<void>FocusLogWindow()
<string>ReplaceSequenceNumber <string>file <float>frame
<void>SetUserData <string>key <string>value
<fpvalue by value>GetUserDataArray()
<void>DeleteUserData <string>key
<void>ClearUserData()
<void>LoadMetadata <string>filename
<void>SaveMetadata <string>filename
Actions:
OK
Here is the relevant documentation page:
thinkboxsoftware.com/xmesh-s … interface/
The XMesh Loader plugin on the other hand is written completely in C++, because there was no need for added MAXScript flexibility in its UI, and scripted plugins are generally slightly slower due to less granular notification management. Of course, it is still completely exposed to MAXScript as any other plugin, so you can construct and set up an XMesh Loader using MAXScript. Note that when saving XMesh data with the Saver, an MS file is written to the output folder that uses MAXScript to create an XMesh Loader if dropped into the 3ds Max viewport. It looks something like this:
(
local theXMeshLoader = XMeshLoader()
local thePath = getFilenamePath (getThisScriptFilename())
local goOn = true
if not doesFileExist (thePath+"\\"+"XMesh_MultiWS1_0000.xmesh" ) do (thePath = @"C:\temp\XMesh\Teapot\v0001\")
if not doesFileExist (thePath+"\\"+"XMesh_MultiWS1_0000.xmesh" ) do ((messagebox "Please ensure you are executing the script from a MAPPED PATH or local drive to automatically resolve the path.
If you are executing from a Network location, make sure the hard-coded path in the script exists." title:"XMesh Source Sequence Path Not Found"); goOn = false)
if goOn == true do (
local theXMeshLayer = LayerManager.getLayerFromName "XMesh Loaders"
if theXMeshLayer == undefined do theXMeshLayer = LayerManager.newLayerFromName "XMesh Loaders"
theXMeshLayer.addnode theXMeshLoader
theXMeshLoader.viewportSequenceID = 0
theXMeshLoader.name = uniquename "XML_XMesh_MultiWS1__"
theXMeshLoader.enableViewportMesh = true
theXMeshLoader.displayMode = 0
theXMeshLoader.displayPercent = 5.0
theXMeshLoader.limitToRange = true
select theXMeshLoader
theXMeshLoader.rangeFirstFrame = 0
theXMeshLoader.rangeLastFrame = 0
theXMeshLoader.viewportSequenceID = 0
theXMeshLoader.proxySequence = ""
theXMeshLoader.renderSequence = thePath + "XMesh_MultiWS1_0000.xmesh"
local theMatLibPath = thePath + "XMesh_MultiWS1_.mat"
if doesFileExist theMatLibPath do (
local theMatLib = loadTempMaterialLibrary theMatLibPath
if theMatLib != undefined do theXMeshLoader.material = theMatLib[1]
)
)
)
The same applies to Krakatoa MX - in fact, it was the first product to use this C++ / MAXScript split, and it has a lot more parts that are scripted, including the UI of the renderer itself, the PRT Loader UI, the Magma Editor, the Particle Data Viewer, the Krakatoa Schematic View, the Krakatoa Explorers, Shadows Manager and so on. Pretty much everything that is not the renderer itself or the core Magma system is in MAXScript, communicating with the core via functions and properties, including the FranticParticles interface.
You can learn a lot more about it here:
thinkboxsoftware.com/krakatoa-maxscript/
The Stoke MX plugin follows the same principles as Krakatoa MX. The Stoke Particle Simulator UI is fully scripted, and you can dissect the MS file to see how the simulator is run by MAXScript calls in the StokeGlobalInterface:
showInterface StokeGlobalInterface
Interface: StokeGlobalInterface
Properties:
.HomeDirectory : TSTR by value : Read
.Version : TSTR by value : Read
.Licensed : bool : Read
.ViewportType : enum : Read
ViewportType enums: {#legacy|#nitrous}
.PointSizeSupported : bool : Read
.LoggingLevel : enum : Read|Write
LoggingLevel enums: {#none|#error|#warning|#progress|#stats|#debug}
.MaxDebuggerIterations : integer : Read|Write
.MaxMarkerCount : integer : Read|Write
Methods:
<void>ShowLicenseDialog()
<Interface>CreateAdvector <string>Type
<Interface>CreateIDAllocator()
<Interface>CreateReflowField <node>Node
<Interface>CreateParticleVelocityField <node>Node <float>Spacing Min:<point3> Max:<point3> BoundsPadding:<integer> RemoveDivergence:<bool>
Min default value: [0,0,0]
Max default value: [0,0,0]
BoundsPadding default value: 5
RemoveDivergence default value: true
<Interface>CreateAdditiveVelocityField <Interface array>Fields
<Interface>CreateKrakatoaGenerator <node>Node JitterRadius:<float> IgnoreIDs:<bool>
JitterRadius default value: 0.0
IgnoreIDs default value: false
<Interface>CreateGeometryGenerator <node>Node Mode:<string> VolumeSpacing:<float> SelectionType:<string> VertexJitterRadius:<float>
Mode default value: "Surface"
VolumeSpacing default value: 10.0
SelectionType default value: "FaceSelection"
VertexJitterRadius default value: 0.0
<Interface>CreateFumeFXGenerator <node>Node
<Interface>CreateParticleSet <string array>ExtraChannels
<void>AdvectParticleSet <Interface>ParticleSet <Interface>Advector <Interface>VelocityField <float>TimeStepSeconds
<void>WriteParticleSet <Interface>ParticleSet <string>FilePath
<void>BeginRenderMode <node array>NodeList
<void>EndRenderMode <node array>NodeList
<enum>GetSourceType <node>Node
GetSourceType enums: {#invalid|#particles|#geometry|#fumefx
<enum>GetVelocityType <node>Node
GetVelocityType enums: {#invalid|#particles|#fumefx|#force|#field
<void>SetTempDirectory <string>Directory
<bool>IsEmberField <node>Node
<Interface>CreateMXSField <node>Node StackIndex:<integer>
StackIndex default value: 0
<void>WriteFXDFile <string>FilePath <node>Node <point3>WSBoundsMin <point3>WSBoundsMax <float>Spacing StackIndex:<integer>
StackIndex default value: 0
<void>WriteField3DFile <string>FilePath <node>Node <point3>WSBoundsMin <point3>WSBoundsMax <float>Spacing StackIndex:<integer>
StackIndex default value: 0
<void>WriteOpenVDBFile <string>FilePath <node>Node <point3>WSBoundsMin <point3>WSBoundsMax <float>Spacing StackIndex:<integer>
StackIndex default value: 0
Actions:
OK
A lot of the other components of Stoke are also scripted plugins, scripted dialogs etc. They are all located in the \Scripts subfolder of the installation, have a look.
The Frost plugin is mostly C++, although it also has some scripted utilities, and is of course fully exposed to MAXScript like any other scene object.
theFrost = Frost()
$Frost:Frost001 @ [0.000000,0.000000,0.000000]
show theFrost
.showIcon : boolean
.iconSize : worldUnits
.updateOnFrostChange : boolean
.updateOnParticleChange : boolean
.nodeList : node array
.pfEventList : node array
.fileList : string array
.loadSingleFrame : boolean
.frameOffset : integer
.limitToRange : boolean
.rangeStartFrame : integer
.rangeEndFrame : integer
.enablePlaybackGraph : boolean
.playbackGraphTime : float
.beforeRangeBehavior : integer
.afterRangeBehavior : integer
.fileLengthUnit : integer
.fileCustomScale : float
.meshingMethod : integer
.enableRenderMesh : boolean
.enableViewportMesh : boolean
.radius : worldUnits
.useRadiusChannel : boolean
.randomizeRadius : boolean
.radiusRandomVariation : float
.radiusRandomSeed : integer
.motionBlurMode : integer
.renderUsingViewportSettings : boolean
.renderMeshingResolution : float
.renderVertRefinementIterations : integer
.viewportMeshingResolution : float
.viewportVertRefinementIterations : integer
.metaballRadiusScale : float
.metaballIsosurfaceLevel : float
.zhuBridsonBlendRadiusScale : float
.zhuBridsonEnableLowDensityTrimming : boolean
.zhuBridsonLowDensityTrimmingThreshold : float
.zhuBridsonLowDensityTrimmingStrength : float
.geometryType : integer
.geometryList : node array
.geometrySelectionMode : integer
.geometrySelectionSeed : integer
.geometrySampleTimeOffsetMode : integer
.geometrySampleTimeMaxRandomOffset : float
.geometrySampleTimeSeed : integer
.geometryOrientationMode : integer
.geometryOrientationLookAtNode : node
.geometryOrientationVectorChannel : string
.geometryOrientationX : float
.geometryOrientationY : float
.geometryOrientationZ : float
.geometryOrientationDivergence : float
.geometryOrientationRestrictDivergenceAxis : boolean
.geometryOrientationDivergenceAxisSpace : integer
.geometryOrientationDivergenceAxisX : float
.geometryOrientationDivergenceAxisY (geometryOrientationDivergenceAxisX) : float
.geometryOrientationDivergenceAxisZ : float
.writeVelocityMapChannel : boolean
.velocityMapChannel : integer
.viewportLoadMode : integer
.viewportLoadPercent : float
.anisotropicRadiusScale : float
.anisotropicWindowScale : float
.anisotropicIsosurfaceLevel : float
.anisotropicMaxAnisotropy : float
.anisotropicMinNeighborCount : integer
.anisotropicPositionSmoothingWindowScale : float
.anisotropicPositionSmoothingWeight : float
.pfEventFilterMode : integer
.materialMode : integer
.undefinedMaterialID : integer
.geometryMaterialIDNodeList : node array
.geometryMaterialIDInList : index array
.geometryMaterialIDOutList : index array
.radiusScale (Radius_Scale) : float
.radiusAnimationMode : integer
.enableRadiusScale : boolean
.geometrySampleTimeBaseMode : integer
.nodeListFlags : int array
.fileListFlags : int array
.meshingResolutionMode : integer
.renderVoxelLength : worldUnits
.viewportVoxelLength : worldUnits
.iconMode : integer
.viewRenderParticles : integer
.tpGroupFilterMode : integer
.tpGroupList : maxObject array
false
Hope this helps.
If you have any specific questions about any of our products, please let me know.