# General Max functions

The Simplygon Max plug-in exports a number of global MaxScript / Python functions. The most common script function is sgsdk_RunPipelineOnSelection which send the currently selected objects to Simplygon for optimization, based on the provided settings-file. See general script functions and examples for more information.

MaxScript

--reset previously set states
sgsdk_Reset()

-- select objects in scene
select $*

-- execute pipeline on selection 
sgsdk_RunPipelineOnSelection "D:/pipelines/reductionPipeline.json" 

Python

from pymxs import runtime as rt

# reset state of previously set parameters
rt.sgsdk_Reset()

# select all objects in scene
rt.select(rt.objects)

# execute Simplygon with the given Pipeline (settings-file)
rt.sgsdk_RunPipelineOnSelection('D:/Pipelines/Reduction.json')

# Supported script functions

The following listing shows the available script functions exposed by the Simplygon Max plug-in (excluding pipeline-settings and shading-networks which are documented separately).

Function Parameter(s) Description
sgsdk_RunPipelineOnSelection int pipelineId Optimizes the selected asset(s) based on the pipeline object.
sgsdk_RunPipelineOnSelection string pipelineFilePath Optimizes the selected asset(s) based on the pipeline file.
sgsdk_Reset Resets all settings in the Max plug-in (not Pipeline-settings).
sgsdk_SetShowProgress bool showProgress Sets the ShowProgress flag. If set, the process runs in its own thread with a progress bar.
sgsdk_MaterialColor string materialName
string channelName
float r
float g
float b
float a
Overrides or creates a color on the specified channel for the specified material.
sgsdk_MaterialTexture string materialName
string channelName
string texturePath
bool isSRGB
Overrides the texture for the specified material.
sgsdk_MaterialTextureMapChannel string materialName
string channelName
int mapChannel
Overrides the mapping channel/UV channel for the specified material.
sgsdk_SetIsVertexColorChannel int mapChannel
bool isVertexColor
Overrides channels > 2 in Max to be used as vertex colors instead of UV coordinates.
sgsdk_UseNonConflictingTextureNames bool useNonConflictingNames Override generation of unique texture names per object when a MaterialLOD is created. By default the value is set to true.
sgsdk_GetTexturePathForCustomChannel string materialName Gets texture path for specified material channel, if exists.
sgsdk_GetMaterialsWithCustomChannels Gets a list of materials that has custom channels.
sgsdk_GetCustomChannelsForMaterial string materialName Gets a list of custom channels for the specified material.
sgsdk_GetProcessedMeshes Returns a list of processed mesh names from last run.
sgsdk_GetProcessedOutputPaths Returns a list of processed file paths from last run.
sgsdk_GetMaterialForMesh string meshName Return material name for the specified mesh.
sgsdk_GetMeshReusesMaterial string meshName Returns material name if the material is shared, otherwise empty string.
sgsdk_GetChannelsForMaterial string materialName Returns a list of material channel names for the specified material.
sgsdk_GetTexturePathForChannel string materialName
string channelName
Returns the texture path for the specified material channel.
sgsdk_GetMaterials Returns a list of material names from the last run.
sgsdk_SetGenerateMaterial bool generateMaterial Specifies whether the plug-in should generate a standard material for baked LODs.
sgsdk_SetUseTangentSpaceNormals int materialName
bool isTangentSpace
Sets the the material to handle normal maps as tangent space normals.
sgsdk_SetMeshNameFormat string formatString Specifies the format string that will be used when importing meshes. {MeshName} and {LODIndex} are reserved keywords and will be replaced with the corresponding values during import. Use sgsdk_SetInitialLODIndex to manually adjust the value of LODIndex.
sgsdk_SetInitialLODIndex int lodIndex Sets the lodIndex to start with for the next import.
sgsdk_ClearGlobalMapping Clears global mapping stored by previous exports.
sgsdk_ImportFromFile string filePath Imports a Simplygon scene from file.
sgsdk_ImportFromFile string filePath
bool copyTextures
Imports a Simplygon scene from file. If copyTextures is true the textures will be imported to Max bitmap directory.
sgsdk_ImportFromFile string filePath
bool copyTextures
bool linkMeshes
Imports a Simplygon scene from file. If copyTextures is true the textures will be imported to Max bitmap directory. If linkMeshes is true the imported meshes will try to access data from scene (bones, hierarchy).
sgsdk_ImportFromFile string filePath
bool copyTextures
bool linkMeshes
bool linkMaterials
Imports a Simplygon scene from file. If copyTextures is true the textures will be imported to Max bitmap directory. If linkMeshes is true the imported meshes will try to access data from scene (bones, hierarchy). If linkMaterials is set the imported meshes will try to reuse existing materials.
sgsdk_ExportToFile string filePath Exports selected Max objects to a Simplygon scene file.
sgsdk_ExportToFile string filePath
bool copyTextures
Exports selected Max objects to a Simplygon scene file. If copyTextures is true the original textures will be exported to a sub-directory of the target path.

# Examples

This section contains various examples written in MaxScript and Python. We've left out some parts of the scripts to keep things as simple as possible.

  • from pymxs import runtime as rt must be declared at the top of each Python script.
  • reductionPipeline settings-path must be declared for both MaxScript and Python scripts where it makes sense.
  • a scene-selection is made before each sgsdk_RunPipelineOnSelection.

# Run Simplygon

To run Simplygon, simply execute the sgsdk_RunPipelineOnSelection along with a Pipeline settings-path or Pipeline settings-object. A Pipeline contains the settings that Simplygon will use during optimization, much like SPL (8.x) and INI (7.x). The optimized result will be returned to Max as soon as the processing has completed.

# Execute Simplygon using a Pipeline file-path

MaxScript

-- reset states
sgsdk_Reset()

-- execute Simplygon with the given pipeline (settings-file)
sgsdk_RunPipelineOnSelection "D:/Pipelines/reductionPipeline.json" 

-- clear all pipelines
sgsdk_ClearPipelines()

Python

from pymxs import runtime as rt

# execute Simplygon with the given pipeline (settings-file)
rt.sgsdk_RunPipelineOnSelection('D:/Pipelines/reductionPipeline.json')

# Execute Simplygon using a Pipeline-object

MaxScript

-- create a reduction pipeline-object
reductionPipeline = sgsdk_CreatePipeline("ReductionPipeline")
...

-- execute Simplygon with the given pipeline-object
sgsdk_RunPipelineOnSelection(reductionPipeline)

-- clear all pipelines
sgsdk_ClearPipelines()

Python

from pymxs import runtime as rt

# create a reduction pipeline-object
reductionPipeline = rt.sgsdk_CreatePipeline('ReductionPipeline')
...

# execute Simplygon with the given pipeline-object
rt.sgsdk_RunPipelineOnSelection(reductionPipeline)

# clear all pipelines
rt.sgsdk_ClearPipelines()

# Mapping data for Import and Export

Some data is incompatible with Simplygon's scene format, such as animation (key frames). To tackle this during manual export and import we are storing some mapping data that makes it possible to preserve this information, this data will reside in memory until manually cleared. Clearing global mapping before an import will result in meshes and materials getting imported solely based on the data in the file. Clear the global mapping manually, preferably before each export.

Clear global mapping data:

MaxScript

-- clear global mapping
sgsdk_ClearGlobalMapping()

Python

from pymxs import runtime as rt

# clear global mapping
rt.sgsdk_ClearGlobalMapping()

Take the example below where we first export a scene to file, then import it back into Max. During export we store mapping data for meshes and materials (guid mapped). At import we do a lookup in the mapping data and if we find a match we can copy rendering properties and link bones more accurately.

MaxScript

-- export the selected Max assets to file
bResult = sgsdk_ExportToFile "D:/Exports/scene.sb"

-- import the specified Simplygon scene into Max
bResult = sgsdk_ImportFromFile "D:/Exports/scene.sb"

Python

from pymxs import runtime as rt

# export the selected Max assets to file
bResult = rt.sgsdk_ExportToFile('D:/Exports/scene.sb')

# import the specified Simplygon scene into Max
bResult = rt.sgsdk_ImportFromFile('D:/Exports/scene.sb')

To gain more control of Import we have exposed two flags, LinkMaterials and LinkMeshes. If LinkMaterials is true we will try to do a lookup in the global mapping, if any, if there is no hit we will do a name based search. If a match is found we will link the existing material(s) to the imported mesh(es). If LinkMaterials is false then new materials will be generated and applied to the mesh(es).

Allow (re)use of original materials:

MaxScript

-- import the specified Simplygon scene into Max, try to reuse existing materials
-- sgsdk_ImportFromFile filePath CopyTextures LinkMeshes LinkMaterials

bResult = sgsdk_ImportFromFile "D:/Exports/scene.sb" false false true

Python

from pymxs import runtime as rt

# import the specified Simplygon scene into Max, try to reuse existing materials
# rt.sgsdk_ImportFromFile (filePath, copyTextures, linkMeshes, linkMaterials)

bResult = rt.sgsdk_ImportFromFile('D:/Exports/scene.sb', False, False, True)

If LinkMeshes is true we will try to do a global lookup, and if that is not working then we will do a named based search. Non-existing mapping may result in loss of data, for example rendering properties and skinning / key frames. If LinkMeshes is false the imported meshes will be considered new meshes, Reductions will not be able to map back to the original hierarchy as an example.

Allow Import to access data on original mesh(es):

MaxScript

-- import the specified Simplygon scene into Max, try to access existing mesh data
-- sgsdk_ImportFromFile filePath copyTextures linkMeshes
-- sgsdk_ImportFromFile filePath copyTextures linkMeshes linkMaterials

bResult = sgsdk_ImportFromFile "D:/Exports/scene.sb" false true false

Python

from pymxs import runtime as rt

# import the specified Simplygon scene into Max, try to access existing mesh data
# rt.sgsdk_ImportFromFile (filePath, copyTextures, linkMeshes)
# rt.sgsdk_ImportFromFile (filePath, copyTextures, linkMeshes, linkMaterials)

bResult = rt.sgsdk_ImportFromFile('D:/Exports/scene.sb', False, True, False)

# Export selected Max assets to file

To export selected Max assets to file, use sgsdk_ExportToFile followed by the output file path. Export is compatible with flags that modifies the output scene in any manner, such as example shading networks and material overrides. The exported file will be saved as Simplygon's proprietary file format.

MaxScript

-- export the selected Max assets to file  
bResult = sgsdk_ExportToFile "D:/Exports/scene.sb"

Python

from pymxs import runtime as rt

# export the selected Max scene to file
bResult = rt.sgsdk_ExportToFile('D:/Exports/scene.sb')

Export selected Max assets along with its textures to file using the CopyTextures flag. Textures will be exported to a sub-directory named "Textures" located in the same directory as the output scene file, in this specific case "D:/Exports/Textures".

MaxScript

-- export the selected Max assets to file (bundle textures)
-- sgsdk_ExportToFile filePath copyTextures

bResult = sgsdk_ExportToFile "D:/Exports/scene.sb" true

Python

from pymxs import runtime as rt

# export the selected Max assets to file (bundle textures)
# rt.sgsdk_ExportToFile(filePath, copyTextures)

bResult = rt.sgsdk_ExportToFile('D:/Exports/scene.sb', True)

# Import saved scene into Max

To import a previously saved file into Max, use sgsdk_ImportFromFile followed by the file path.

Note: The importer is part of the standard Simplygon pipeline (export -> process -> import) and can not create complex materials. New materials will get imported as Blinn / Phong with one (the first) texture available for the given material channel. For complex (node-based) material import we recommend a manual approach.

MaxScript

-- import the specified Simplygon scene into Max
bResult = sgsdk_ImportFromFile "D:/Exports/scene.sb"

Python

from pymxs import runtime as rt

# import the specified Simplygon scene into Max
bResult = rt.sgsdk_ImportFromFile('D:/Exports/scene.sb')

To specify wether the textures should be copied to a local directory, use the CopyTextures flag. Keep in mind that LinkMaterials may override this setting, if the imported asset is reusing existing scene materials it will also use the original textures. If CopyTextures is set the textures will get imported from it's source destination to Max's texture directory. If CopyTextures is not set the imported (generated) material's texture paths will point to the imported scene's destination textures.

MaxScript

-- export the selected Max assets to file (copy textures into Max's texture directory)
-- sgsdk_ImportFromFile filePath copyTextures

bResult = sgsdk_ImportFromFile "D:/Exports/scene.sb" true

Python

from pymxs import runtime as rt

# export the selected Max assets to file (copy textures into Max's texture directory)
# rt.sgsdk_ImportFromFile(filePath, copyTextures)

bResult = rt.sgsdk_ImportFromFile('D:/Exports/scene.sb', True)

# Specify name of processed / imported meshes

Renaming of processed / imported meshes can now be done by using sgsdk_SetMeshNameFormat. {MeshName} and {LODIndex} will be replaced internally if present. If meshes with the given name already exists they will get assigned a new indexed name, usually the original name appended by a unique number.

As scenes can be exported and imported in an unordered manner we've also added sgsdk_SetInitialLODIndex which specifies the starting index for LODIndex. So if you want to import a scene from disk and want to use LODIndex = 5 for all meshes found in the scene, that is now possible.

MaxScript

sgsdk_SetInitialLODIndex 1
sgsdk_SetMeshNameFormat "{MeshName}_LOD{LODIndex}"

bResult = sgsdk_ImportFromFile "D:/Exports/scene.sb"

Python

from pymxs import runtime as rt

rt.sgsdk_SetInitialLODIndex(1)
rt.sgsdk_SetMeshNameFormat('{MeshName}_LOD{LODIndex}')

bResult = rt.sgsdk_ImportFromFile('D:/Exports/scene.sb')

# Rename processed / imported meshes using custom attributes

MaxScript

-- fetch processed mesh names from Simplygon
processedMeshes = sgsdk_GetProcessedMeshes()

-- for all mesh names, print attributes
-- and rename objects to original name
for mesh in processedMeshes do
(
    mMesh = getNodeByName mesh

    maxDeviation = getUserProp mMesh "MaxDeviation" 
    print (".MaxDeviation: " + maxDeviation as string)

    sceneRadius = getUserProp mMesh "SceneRadius" 
    print (".SceneRadius: " + sceneRadius as string)

    originalNodeName = getUserProp mMesh "OriginalNodeName" 
    print (".OriginalNodeName: " + originalNodeName as string)

    intendedNodeName = getUserProp mMesh "IntendedNodeName" 
    print (".IntendedNodeName: " + intendedNodeName as string)

    importedNodeName = getUserProp mMesh "ImportedNodeName" 
    print (".ImportedNodeName: " + importedNodeName as string)

    mMesh.name = originalNodeName
)

Python

from pymxs import runtime as rt

# fetch processed mesh names from Simplygon
processedMeshes = rt.sgsdk_GetProcessedMeshes()

# for all mesh names, print attributes
# and rename objects to original name
for mesh in processedMeshes:
    mMesh = rt.getNodeByName(mesh)

    maxDeviation = rt.getUserProp(mMesh, 'MaxDeviation')
    print('.MaxDeviation: ' + str(maxDeviation))

    sceneRadius = rt.getUserProp(mMesh, 'SceneRadius')
    print('.SceneRadius: ' + str(sceneRadius))

    originalNodeName = rt.getUserProp(mMesh, 'OriginalNodeName')
    print('.OriginalNodeName: ' + str(originalNodeName))

    intendedNodeName = rt.getUserProp(mMesh, 'IntendedNodeName')
    print('.IntendedNodeName: ' + str(intendedNodeName))

    importedNodeName = rt.getUserProp(mMesh, 'ImportedNodeName')
    print('.ImportedNodeName: ' + str(importedNodeName))

    mMesh.name = originalNodeName

# Process scene from file

We've added the function sgsdk_RunPipelineOnFile to allow users to be able to process scenes from file. The InputSceneFile flag specifies which file to optimize while OutputSceneFile specifies the target file path of the optimized scene.

If the pipeline is cascaded the output scene names will be appended with "_LOD" prefix and then indexed from 1 to *, where the index increments for each cascaded level. The output texture folder for each cascaded scene will be "Textures/LOD1" for LOD1, "Textures/LOD2" for LOD2 and so on. The output file names can be fetched with sgsdk_GetProcessedOutputPaths which can then be used at import.

MaxScript

-- process input file and save optimized result to output file.
bResult = sgsdk_RunPipelineOnFile pipeline "D:/Exports/scene.sb" "D:/Exports/scene_processed.sb"

-- get processed output file paths
processedOutputFiles = sgsdk_GetProcessedOutputPaths()

-- set import format string
sgsdk_SetMeshNameFormat "{MeshName}_LOD{LODIndex}"

lodIndex = 1
for path in processedOutputFiles do
(
    -- update lodIndex
    sgsdk_SetInitialLODIndex lodIndex

    -- import Simplygon scene from file
    bResult = sgsdk_ImportFromFile path
    lodIndex += 1
)

Python

from pymxs import runtime as rt

# process input file and save optimized result to output file.
bResult = rt.sgsdk_RunPipelineOnFile(pipeline, 'D:/Exports/scene.sb','D:/Exports/scene_processed.sb')

# get processed output file paths
processedOutputFiles = rt.sgsdk_GetProcessedOutputPaths()

# set import format string
rt.sgsdk_SetMeshNameFormat('{MeshName}_LOD{LODIndex}')

# import scene(s)
lodIndex = 1
for path in processedOutputFiles:
    # update lodIndex
    rt.sgsdk_SetInitialLODIndex(lodIndex)

    # import Simplygon scene from file
    bResult = sgsdk_ImportFromFile(path)
    lodIndex += 1

# Calculate switch-distance

Simplygon writes down custom attributes for each mesh transform that can be used to calculate the recommended LOD-Switch distance. The attributes are MaxDeviation which indicates the pixel deviation compared to the original asset, and SceneRadius which is the total radius of the scene that was sent for processing. All meshes processed at the same time will have the same attribute values, thus also switch distance. MaxDeviation is currently available for Reduction and Remeshing. Aggregation do not have a switch distance as all it does is to merges geometry (and possibly materials), in this example the MaxDeviation gets set to 0 where the attribute does not exists.

Attributes:

LOD-switch attributes

To calculate the switch distance you also need to take a few other parameters into account, such as resolution and field-of-view (FOV). See the formula below (there is a more detailed description in the API documentation).

Python

import MaxPlus
from pymxs import runtime as rt

# Object is the mesh transform
def calculateLODSwitchDistance(object):
    # Reads the .SceneRadius attribute
    radius = SimplygonWrapper.getSceneRadius(object)

    # Reads the MaxDeviation attribute
    deviation = SimplygonWrapper.getMaxDeviation(object)

    # Calculates the pixel size (OnScreenSize)
    pixelsize = (radius*2)/deviation

    # Screen resolution and FOV
    curView = MaxPlus.ViewportManager.GetActiveViewport()
    size = rt.getViewSize()
    screenheight = size[1]
    screenwidth = size[2]
    fov_y =  rt.curView.GetFOV()

    # Calculates screen ratio
    screen_ratio = float(pixelsize) / float(screenheight)
    normalized_distance = 1.0 / (math.tan(fov_y / 2))

    # The view-angle of the bounding sphere rendered on-screen.
    bsphere_angle = math.atan(screen_ratio / normalized_distance)

    # The distance in real world units from the camera to the center of the
    # bounding sphere. Not to be confused with normalized distance.
    distance = radius / math.sin(bsphere_angle)
    return distance

# Material color override

To override a color of a material channel, enter the material name, the name of the channel and the RGBA values for a specific color. In the example below we choose to set the diffuse channel of "MyMaterial" to blue. If the material channel does not exist it will be created.

MaxScript

sgsdk_MaterialColor "MyMaterial" "DiffuseColor" 0.0 0.0 1.0 1.0

Python

from pymxs import runtime as rt

rt.sgsdk_MaterialColor('MyMaterial', 'DiffuseColor', 0.0, 0.0, 1.0, 1.0)

# Material texture override

To override a texture of a material channel, enter the name of the material, the name of the channel and at last the path to the target texture. In the example below we choose to set a diffuse texture to the diffuse channel of the material. The last flag specifies if the texture should be handled as in sRGB color space or not. If the material channel does not exist it will be created.

MaxScript

sgsdk_MaterialTexture "MyMaterial" "DiffuseColor" "D:/Textures/MyTexture.png" false

Python

from pymxs import runtime as rt

rt.sgsdk_MaterialTexture('MyMaterial', 'DiffuseColor', 'D:/Textures/MyTexture.png', False)

# Selection sets

Object selection sets are exported from Max's selection sets automatically. There is no need to script the creation of selection sets but it is still required to assign which selection set to use for features such as Bone Lock and Clipping Geometry.

Please see the Pipeline documentation for more information on how to set specific settings parameters.

# Simplygon API examples (Python)

This section includes examples of how to use the Simplygon Python API to optimize the scene. A pipeline encapsulates a certain functionality and exposes settings which can be tweaked for the expected result. A Pipeline can also be executed internally or externally. If it is to be executed externally the BatchProcessor has to be specified in the pipeline (through SetSimplygonBatchPath).

Note that some of these API examples can be done using Pipelines, if some features are not exposed in Pipelines or that some custom behavior is required then Simplygon API is the way to go.

# Reduction using ReductionPipeline

The first part exports the selected Max scene to file. The second part loads a pre-defined reduction pipeline from file and optimizes the previously saved scene. The third part imports the processed scene back into Max. The export and import could be done manually if preferred.

from pymxs import runtime as rt
from simplygon import simplygon_loader
from simplygon import Simplygon
import gc

def run_pipeline(sg):
    # Set Max's handedness setting
    sg.SetGlobalDefaultTangentCalculatorTypeSetting(Simplygon.ETangentSpaceMethod_Autodesk3dsMax)

    # Create pipeline serializer
    sgSerializer = sg.CreatePipelineSerializer()
    if sgSerializer.IsNull():
        print('Failed to create pipeline serializer.')
        exit(-100)

    # Load pipeline settings from file
    sgPipeline = sgSerializer.LoadPipelineFromFile('D:/Pipelines/Reduction.json')
    if sgPipeline.IsNull():
    print('Invalid pipeline definition file')
        exit(-200)

    # Run pipeline (in this process)
    sgPipeline.RunSceneFromFile('D:/Assets/ExportedScene.sb', 'D:/Assets/ProcessedScene.sb', Simplygon.EPipelineRunMode_RunInThisProcess)

# Load Max scene
rt.resetMaxFile(rt.Name('noPrompt'))
rt.importFile('D:/Assets/MyScene.fbx', rt.Name('noPrompt'))

# Clear previous export mapping
rt.sgsdk_ClearGlobalMapping()

# Select all objects
rt.select(rt.objects)

# Export scene to file
bResult = rt.sgsdk_ExportToFile('D:/Assets/ExportedScene.sb', False)

# Initialize Simplygon
sg = simplygon_loader.init_simplygon()

# Reduce exported file
run_pipeline(sg)

# Format import string
rt.sgsdk_SetInitialLODIndex(1)
rt.sgsdk_SetMeshNameFormat('{MeshName}_LOD{LODIndex}')

# Import processed file into Max, LOD-index = 1,
# CopyTextures = False, LinkMeshes = True, LinkMaterials = True
bResult = rt.sgsdk_ImportFromFile('D:/Assets/ProcessedScene.sb', False, True, True)

# De-initialize Simplygon
sg = None
gc.collect()

# Reduction using ReductionProcessor

The first part exports the selected Max scene to file. The second part sets up a reduction processor and optimizes the previously saved scene. The third part imports the processed scene back into Max. The export and import could be done manually if preferred.

from pymxs import runtime as rt
from simplygon import simplygon_loader
from simplygon import Simplygon
import gc

def run_reduction (sg):
    # Set Max's handedness setting
    sg.SetGlobalDefaultTangentCalculatorTypeSetting(Simplygon.ETangentSpaceMethod_Autodesk3dsMax)

    # Load the scene from file
    lodScene = sg.CreateScene()
    lodScene.LoadFromFile('D:/Assets/ExportedScene.sb')

    # Create the reduction-processor, and set which scene to reduce
    reductionProcessor = sg.CreateReductionProcessor()
    reductionProcessor.SetScene(lodScene)

    # The reduction settings object contains settings pertaining to the actual decimation
    reductionSettings = reductionProcessor.GetReductionSettings()

    # Try, when possible to reduce symmetrically
    reductionSettings.SetKeepSymmetry(True)

    # Auto-detect the symmetry plane, if one exists. Can, if required, be set manually instead
    reductionSettings.SetUseAutomaticSymmetryDetection(True)

    # Drastically increases the quality of the LODs normals, at the cost of extra processing time
    reductionSettings.SetUseHighQualityNormalCalculation(True)

    # Choose between "fast" and "consistent" processing
    reductionSettings.SetReductionHeuristics(Simplygon.EReductionHeuristics_Consistent)

    # The reduction stops when any of the targets below is reached
    reductionSettings.SetReductionTargetStopCondition(Simplygon.EStopCondition_Any)

    # Selects which targets should be considered when reducing
    reductionSettings.SetReductionTargetTriangleRatioEnabled(True)
    reductionSettings.SetReductionTargetTriangleCountEnabled(True)
    reductionSettings.SetReductionTargetMaxDeviationEnabled(True)
    reductionSettings.SetReductionTargetOnScreenSizeEnabled(True)

    # Targets at 50% of the original triangle count
    reductionSettings.SetReductionTargetTriangleRatio(0.5)
    reductionSettings.SetReductionTargetTriangleCount(10)  # Targets when only 10 triangle remains

    # Targets when an error of the specified size has been reached. As set here it never happens
    reductionSettings.SetReductionTargetMaxDeviation(1e27)

    # Targets when the LOD is optimized for the selected on screen pixel size
    reductionSettings.SetReductionTargetOnScreenSize(50)

    # The repair settings object contains settings to fix the geometries
    repairSettings = reductionProcessor.GetRepairSettings()
    repairSettings.SetTJuncDist(0.0)  # Removes t-junctions with distance 0.0f
    repairSettings.SetWeldDist(0.0)  # Welds overlapping vertices

    # The normal calculation settings deal with the normal-specific reduction settings
    normalSettings = reductionProcessor.GetNormalCalculationSettings()

    # If True, this will turn off normal handling in the reducer and recalculate them all
    # afterwards instead.
    normalSettings.SetReplaceNormals(False)

    # Run the actual processing.
    # After this, the set geometry will have been reduced according to the settings
    reductionProcessor.RunProcessing()

    # Save the processed scene to file
    lodScene.SaveToFile('D:/Assets/ProcessedScene.sb')

# Load Max scene
rt.resetMaxFile(rt.Name('noPrompt'))
rt.importFile('D:/Assets/MyScene.fbx', rt.Name('noPrompt'))

# Clear previous export mapping
rt.sgsdk_ClearGlobalMapping()

# Select all objects
rt.select(rt.objects)

# Export scene to file
bResult = rt.sgsdk_ExportToFile('D:/Assets/ExportedScene.sb', False)

# Initialize Simplygon
sg = simplygon_loader.init_simplygon()

# Run reduction
run_reduction(sg)

# Format import name
rt.sgsdk_SetInitialLODIndex(1)
rt.sgsdk_SetMeshNameFormat('{MeshName}_LOD{LODIndex}')

# Import processed file into Max, LOD-index = 1,
# CopyTextures = False, LinkMeshes = True, LinkMaterials = True
bResult = rt.sgsdk_ImportFromFile('D:/Assets/ProcessedScene.sb', False, True, True)

# De-initialize Simplygon
sg = None
gc.collect()

# Aggregation (Hollow shell) using AggregationProcessor

This example demonstrates how to use the aggregation processor to combine objects and remove interiors using the geometry culling functionality. The selected scene gets exported to file, optimized through the AggregationProcessor, and imported back into Max.

Hollow shell can also be accomplished using the AggregationPipeline by enabling GeometryCulling.

import os
from pymxs import runtime as rt
import random
from simplygon import simplygon_loader
from simplygon import Simplygon
import gc

def AddToSelectionSets(sceneNode, processSet, clippingSet):
    for i in xrange(0, sceneNode.GetChildCount()):
        child = sceneNode.GetChild(i)
        if child.IsA('ISceneMesh'):

            # Add clipping geometry to the clippingSet set. Otherwise to the processing set.
            if child.GetName().startswith('clipgeom'):
                clippingSet.AddItem(child.GetNodeGUID())
            else:
                processSet.AddItem(child.GetNodeGUID())
        AddToSelectionSets(child, processSet, clippingSet)

def RunHollowShell(scene, useClippingTerrain) :
    print('RunHollowShell')
    print('\tuseClippingTerrain=' + str(useClippingTerrain))

    aggregationProcessor = sg.CreateAggregationProcessor()
    aggregationProcessor.SetScene(scene)

    aggregatorSettings = aggregationProcessor.GetAggregationSettings()

    # This is the base setting that enables the culling of interiors.
    # All triangles on the insides of watertight meshes will be removed,
    # since they are redundant for rendering. It is possible to manipulate visibility
    # settings to get similar results from the visibility-driven triangle culling
    # functionality, but then you can't use clipping geometries or clipping planes.
    aggregatorSettings.SetEnableGeometryCulling(True)

    # This controls the precision of the culling. Higher values will be more accurate
    # while lower values will be faster. Increase if you're getting bad cracks.
    aggregatorSettings.SetGeometryCullingPrecision(0.2)

    aggregatorSettings.SetMergeGeometries(True)

    if (useClippingTerrain):
        # We can use that terrain to further cull the the asset.

        # Make selection set to put processing and clipping geometry into
        clippingSet = sg.CreateSelectionSet()
        processSet = sg.CreateSelectionSet()

        # Split up the objects so that all meshes that starts with "clipgeom" is placed
        # into the clipping set. The rest of the meshes are assumed as geometry to merge and cut.
        # What is culled and what is removed is determined by the triangle winding of the
        # clipping geometry, so whatever parts of the process geometry is on the positive
        # halfspace of the clipping geometry will remain while everything else will be removed.
        # Since this is determined by triangle winding, the culling will not work if there are
        # culling geometry backfaces in the halfspace you want to remain.
        AddToSelectionSets(scene.GetRootNode(), processSet, clippingSet)
        clippingSetId = scene.GetSelectionSetTable().AddSelectionSet(clippingSet)
        processSetId = scene.GetSelectionSetTable().AddSelectionSet(processSet)

        # Setup the settings for using clipping geom
        aggregationProcessor.GetGeometryCullingSettings().SetUseClippingGeometry(True)
        aggregatorSettings.SetProcessSelectionSetID(processSetId)
        aggregationProcessor.GetGeometryCullingSettings().SetClippingGeometrySelectionSetID(clippingSetId)

    # Run the process
    aggregationProcessor.RunProcessing()

    return scene

def createAssets():
    print 'Create assets here!'
    # create sphere named s0
    # create plane named clipgeom that intersects the sphere

def optimize_scene(sg, intermediate_file):
    scene = sg.CreateScene()
    scene.LoadFromFile(intermediate_file)

    # Before any specific processing starts, set global variables.
    # Using 3ds Max tangent space calculation.
    sg.SetGlobalDefaultTangentCalculatorTypeSetting(Simplygon.ETangentSpaceMethod_Autodesk3dsMax)

    # The aggregation processor is used to combine all input meshes into
    # a single output mesh, and a terrain clipping geometry is used to
    # close the concavity at the bottom of the rockpile, allowing
    # more aggressive culling of inside triangles while looking identical
    # as long as it is rendered along with the clipping terrain.
    optimizedScene = RunHollowShell(scene, True)

    scene.SaveToFile(intermediate_file)

# Reset scene and create assets
rt.resetMaxFile(rt.Name('noPrompt'))
createAssets()

# Load the scene from the Max format, export the data into simplygon format and import
# the file into a simplygon scene.
rt.select(rt.objects)
intermediate_file = os.path.dirname(__file__)+'/_export.sb'
bResult = rt.sgsdk_ExportToFile(intermediate_file, True)

# Initialize Simplygon
sg = simplygon_loader.init_simplygon()

# Optimize scene
optimize_scene(sg, intermediate_file)

# Format import string
rt.sgsdk_SetInitialLODIndex(1)
rt.sgsdk_SetMeshNameFormat('{MeshName}_LOD{LODIndex}')

# Import optimized scene
bResult = rt.sgsdk_ImportFromFile(intermediate_file)
os.remove(intermediate_file)

# De-initialize Simplygon
sg = None
gc.collect()