Keep original materials during aggregation
Written by Jesper Tingvall, Product Expert, Simplygon
Disclaimer: The code in this post is written using version 10.2.11500.0 of Simplygon and Blender 3.6. If you encounter this post at a later stage, some of the API calls might have changed. However, the core concepts should still remain valid.
Introduction
In this blog we'll showcase how to keep original materials during material merging. This enables you to keep materials that require special shaders separated during optimization.
This blog is a continuation of Aggregation with multiple output materials and will use the same code skeleton. Make sure to read it first.
Prerequisites
This example will use the Simplygon integration in Blender, but the same concepts can be applied to all other integrations of the Simplygon API. If you run it in other integrations you need to change the material casting settings as described in this blog.
Problem to solve
We have an asset that has several opaque and transparent materials as well as some with special shaders. We want to optimize draw calls by merging all opaque materials into one, and all transparent materials into another materials. We want the materials that uses special shaders to remain so we can reuse the original materials and textures for these ones.
The asset we will use in this blog are from the following sources. The assets have been adapted to suit the content of the blog.
The mystical fluids in the goblets are the ones we want to keep the original materials for.
Solution
At a high level our solution will work the like following. The first step is to divide up the materials into which ones should be aggregated and which should be kept. After that we will save the original material IDs and UVs. Aggregation is performed and after that the materials that should be kept are restored.
We are going to extend the script we introduced in Aggregation with multiple output materials. The only new functions and changes will be explained in this blog.
Divide up materials
We'll use the same mechanism as in the previous blog, but add another case, MaterialType.KEEP_ORIGINAL
. Materials classified as this will be restored after aggregation. The rest of the code is kept as it is, so we'll aggregate all transparent materials into one material and all opaque materials into another one.
# The different kind of material channels we are going to bake our asset into.
class MaterialTypes(IntEnum):
OPAQUE = 0
TRANSPARENT = 1
KEEP_ORIGINAL = 4
In our example we'll mark materials to be kept by that their name starts with a "_"
.
def get_material_type(scene: Simplygon.spScene, material_id: int) -> MaterialTypes:
"""Return which material type the material is."""
material = scene.GetMaterialTable().GetMaterial(material_id)
material_name = material.GetName().lower()
# Material names starting with _ will not be aggregated.
if material_name[0] == "_":
return MaterialTypes.KEEP_ORIGINAL
elif material.IsTransparent():
return MaterialTypes.TRANSPARENT
else:
return MaterialTypes.OPAQUE
Optimize scene
Our optimize_scene
function is almost identical with the one in previous blog, but we have some changes. Before performing the aggregation, we call save_original_material_data
which puts the original material ID and UVs into two user defined fields so we can restore them later. We also figure out the dummy material ID for MaterialTypes.KEEP_ORIGINAL
. We do not create a new texture table, but instead reuse the old one.
def optimize_scene(sg: Simplygon.ISimplygon, scene : Simplygon.spScene):
"""Optimize scene with aggregator."""
material_types = get_material_types(scene)
aggregation_processor = create_aggregator_processor(sg, scene, material_types)
save_original_material_data(scene, ORIGINAL_MATERIAL_IDS_FIELD, ORIGINAL_UVS_FIELD)
# Start the aggregation process.
print("Running aggregator...")
aggregation_processor.RunProcessing()
# Create temporary material and texture tables the output scene will use.
new_material_table = sg.CreateMaterialTable()
new_texture_table = scene.GetTextureTable() #sg.CreateTextureTable()
# Remember ID of the material we should restore later
keep_material_id = get_material_type_index(MaterialTypes.KEEP_ORIGINAL, material_types)
# Cast all materials
cast_materials(sg, scene, aggregation_processor, material_types, new_material_table, new_texture_table)
Since the material table is changed after casting; we have new materials, we need to create a dictionary which holds mapping between old material IDs and new material IDs for the materials we want to keep.
Lastly, we restore original material IDs and UV coordinates with restore_original_material_data
.
material_remapping = {}
material_table = scene.GetMaterialTable()
for material_id in range(0, material_table.GetMaterialsCount()):
if get_material_type(scene, material_id) == MaterialTypes.KEEP_ORIGINAL:
material = material_table.GetMaterial(material_id)
new_id = new_material_table.AddItem(material)
material_remapping[material_id] = new_id
# We can not clear texture table as the textures are used for original materials we want to keep.
# Clear material table and copy in our new materials
scene.GetMaterialTable().Clear()
scene.GetMaterialTable().Copy(new_material_table)
# Restore original materials
restore_original_material_data(scene, keep_material_id, material_remapping, ORIGINAL_MATERIAL_IDS_FIELD, ORIGINAL_UVS_FIELD)
Saving original material data
To keep the original materials, we need to save original material ID and UVs as the aggregator will write new data. We will save this data as two user fields in our geometry data. User fields are kept after aggregation and reduction. It is a useful way of saving custom data in your model.
There are three types of user fields, per-vertex, per-triangle and per-corner fields. Material ID is saved as a triangle field and UVs as corner field. We are going to use these names to refer to our fields.
ORIGINAL_MATERIAL_IDS_FIELD = "OriginalMaterialIds"
ORIGINAL_UVS_FIELD = "OriginalUVs"
To iterate through all models in our scene we create a selection set containing all scene nodes of type "SceneMesh". We can then loop through the selection set and get the geometry for each mesh.
def save_original_material_data(scene : Simplygon.spScene, original_material_ids_field_name : str, original_uvs_field_name : str):
scene_meshes_selection_set_id = scene.SelectNodes("SceneMesh")
scene_meshes_selection_set = scene.GetSelectionSetTable().GetSelectionSet(scene_meshes_selection_set_id)
# Loop through all meshes in the scene
for node_id in range(scene_meshes_selection_set.GetItemCount()):
scene_mesh = Simplygon.spSceneMesh.SafeCast(scene.GetNodeByGUID( scene_meshes_selection_set.GetItem(node_id)))
geometry = scene_mesh.GetGeometry()
if not geometry:
print("Scene without geometry, skipping")
continue
if not geometry.GetMaterialIds():
print("Geometry with no material IDs, skipping")
continue
if not geometry.GetTexCoords(0):
print("Geometry with no TexCoords, skipping")
continue
We will now create a user field to save material ID. We can see that material ID is a triangle attribute, so a user triangle field sounds good.
When adding a user field we need to specify the field type and tuple size. For material IDs the type is TYPES_ID_RID
and we have a size of 1. We can check GetMaterialIDs
to validate how it is stored.
Since we specified that the field was of type TYPES_ID_RID
we need to cast its array to a RidArray
. Using DeepCopy
we can copy over our material ID field to our newly created triangle attribute field.
# Save material IDs
geometry.AddBaseTypeUserTriangleField( Simplygon.EBaseTypes_TYPES_ID_RID, original_material_ids_field_name , 1 )
saved_original_material_ids = geometry.GetUserTriangleField(original_material_ids_field_name)
if saved_original_material_ids != None:
saved_original_material_ids_array = Simplygon.spRidArray.SafeCast( saved_original_material_ids )
material_ids = geometry.GetMaterialIds()
saved_original_material_ids_array.DeepCopy(material_ids)
If we look at GetTexCoords
we can see that these are stored as a RealArray
with a tuple of size 2 per corner. So we'll save them to a field created with AddBaseTypeUserCornerField
. We specify that the type is EBaseTypes_TYPES_ID_REAL
and a tuple size of 2. Then we copy the first TexCoords
field into our newly created corner fields.
# Save UVs
geometry.AddBaseTypeUserCornerField( Simplygon.EBaseTypes_TYPES_ID_REAL, original_uvs_field_name , 2 )
saved_original_uvs = geometry.GetUserCornerField( original_uvs_field_name )
if saved_original_uvs != None:
saved_original_uvs_array = Simplygon.spRealArray.SafeCast( saved_original_uvs )
original_uvs = geometry.GetTexCoords(0)
saved_original_uvs_array.DeepCopy(original_uvs)
scene_meshes_selection_set = None
scene.GetSelectionSetTable().RemoveItem( scene_meshes_selection_set_id )
Change of casting
We are going to introduce one small change to the cast_materials
function. We will make so it does not cast any textures for our MaterialTypes.KEEP_ORIGINAL
dummy material. Casting textures for this material would be a waste of time as we will not use those textures.
def cast_materials(sg : Simplygon.ISimplygon, scene : Simplygon.spScene, processor : Simplygon.spAggregationProcessor, material_types : list[MaterialTypes], material_table : Simplygon.spMaterialTable, texture_table : Simplygon.spTextureTable):
"""Cast all materials in scene to new texture and material tables."""
for j in range(0, len(material_types)):
print(f"Creating output material for {material_types[j]}...")
# Add new material for each kind
new_material = sg.CreateMaterial()
new_material.SetName(f"{material_types[j].name}")
material_table.AddMaterial( new_material )
# Do not cast for dummy material KEEP_ORIGINAL as these textures will not be used.
if material_types[j] == MaterialTypes.KEEP_ORIGINAL:
continue
...
Restore original material data
We'll now add a function which restores the data we saved in save_original_material_data
function. We start by creating a selection set of all SceneMesh
nodes and iterate through them.
def restore_original_material_data(scene, keep_material_id : int, material_map : dict[int, int], original_material_ids_field_name : str, original_uvs_field_name : str):
scene_meshes_selection_set_id = scene.SelectNodes("SceneMesh")
scene_meshes_selection_set = scene.GetSelectionSetTable().GetSelectionSet(scene_meshes_selection_set_id)
# Loop through all meshes in the scene
for node_id in range(scene_meshes_selection_set.GetItemCount()):
scene_mesh = Simplygon.spSceneMesh.SafeCast(scene.GetNodeByGUID( scene_meshes_selection_set.GetItem(node_id)))
geometry = scene_mesh.GetGeometry()
if not geometry:
print("Scene without geometry, skipping")
continue
if not geometry.GetMaterialIds():
print("Geometry with no material IDs, skipping")
continue
if not geometry.GetTexCoords(0):
print("Geometry with no TexCoords, skipping")
continue
material_ids = geometry.GetMaterialIds()
We will now restore original UVs and material IDs for the parts of the model where the dummy material is assigned. We start by getting our user field with original UV coordinates. Then we iterate through the UV coordinates and see which triangle ID those are assigned
To calculate the triangle ID from UV index we'll do the following calculation. Each corner has 2 UV coordinates. Each triangle has 3 corners. Thus, the triangle ID can be calculated by triangle_id = uv_coordinate_id / 2 / 3
. If the triangle ID has our dummy KEEP_ORIGINAL
material, then we restore the original UV coordinates.
# load original UVs
saved_original_uvs = geometry.GetUserCornerField( original_uvs_field_name )
if saved_original_uvs != None:
saved_original_uvs_array = Simplygon.spRealArray.SafeCast( saved_original_uvs )
current_uvs = geometry.GetTexCoords(0)
for i in range(0, current_uvs.GetItemCount()):
triangle_id = math.floor(i / 2.0 /3.0)
current_material_id = material_ids.GetItem(triangle_id)
if current_material_id == keep_material_id:
current_uvs.SetRealItem(i, saved_original_uvs_array.GetRealItem(i))
We'll do almost the same for material ID. We iterate through all triangles and if the material Id is our dummy KEEP_ORIGINAL
we restore the original material ID. We need to refer to our material_map
dictionary as the ID might have changed.
Lastly we clean up by removing the temporary selection set.
# Load saved material IDs
saved_original_material_ids = geometry.GetUserTriangleField( original_material_ids_field_name )
if saved_original_material_ids != None:
saved_original_material_ids_array = Simplygon.spRidArray.SafeCast( saved_original_material_ids )
for triangle_id in range(0, material_ids.GetItemCount()):
original_material_id = saved_original_material_ids_array.GetItem(triangle_id)
current_material_id = material_ids.GetItem(triangle_id)
if current_material_id == keep_material_id:
material_ids.SetItem(triangle_id, material_map[original_material_id])
scene_meshes_selection_set = None
scene.GetSelectionSetTable().RemoveItem( scene_meshes_selection_set_id )
Result
Let's run the script and inspect the result.
A side note is that in Blender we always get back a copy of the original materials since it uses gltf as intermediary step. We can change the materials back into the original material after import.
Let us also look at the texture atlases.
Complete script
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
from enum import IntEnum
import bpy
import gc
import math
from simplygon10 import simplygon_loader
from simplygon10 import Simplygon
# Temporary file names.
TMP_DIR = "C:/Tmp/"
IN_FILE = "input.glb"
OUTPUT_FILE = "output.glb"
# Texture size. Change parameters for quality
TEXTURE_SIZE = 4096
# Name of all material channels we want to bake
MATERIAL_CHANNELS = [("Diffuse", Simplygon.EImageColorSpace_sRGB), ("Roughness", Simplygon.EImageColorSpace_Linear), ("Metalness", Simplygon.EImageColorSpace_Linear), ("Normals", Simplygon.EImageColorSpace_Linear)]
# Blender specific name for Normal channel.
NORMAL_CHANNEL = "Normals"
# The different kind of material channels we are going to bake our asset into.
class MaterialTypes(IntEnum):
OPAQUE = 0
TRANSPARENT = 1
AVATAR = 2
TATTOO = 3
KEEP_ORIGINAL = 4
ORIGINAL_MATERIAL_IDS_FIELD = "OriginalMaterialIds"
ORIGINAL_UVS_FIELD = "OriginalUVs"
def export_selection(sg: Simplygon.ISimplygon, file_path: str) -> Simplygon.spScene:
"""Export the current selected objects into Simplygon."""
bpy.ops.export_scene.gltf(filepath = file_path, use_selection=True)
scene_importer = sg.CreateSceneImporter()
scene_importer.SetImportFilePath(file_path)
scene_importer.Run()
scene = scene_importer.GetScene()
return scene
def import_results(sg: Simplygon.ISimplygon, scene, file_path : str):
"""Import the Simplygon scene into Blender."""
scene_exporter = sg.CreateSceneExporter()
scene_exporter.SetExportFilePath(file_path)
scene_exporter.SetScene(scene)
scene_exporter.Run()
bpy.ops.import_scene.gltf(filepath=file_path)
def save_original_material_data(scene : Simplygon.spScene, original_material_ids_field_name : str, original_uvs_field_name : str):
scene_meshes_selection_set_id = scene.SelectNodes("SceneMesh")
scene_meshes_selection_set = scene.GetSelectionSetTable().GetSelectionSet(scene_meshes_selection_set_id)
# Loop through all meshes in the scene
for node_id in range(scene_meshes_selection_set.GetItemCount()):
scene_mesh = Simplygon.spSceneMesh.SafeCast(scene.GetNodeByGUID( scene_meshes_selection_set.GetItem(node_id)))
geometry = scene_mesh.GetGeometry()
if not geometry:
print("Scene without geometry, skipping")
continue
if not geometry.GetMaterialIds():
print("Geometry with no material IDs, skipping")
continue
if not geometry.GetTexCoords(0):
print("Geometry with no TexCoords, skipping")
continue
# Save material IDs
geometry.AddBaseTypeUserTriangleField( Simplygon.EBaseTypes_TYPES_ID_RID, original_material_ids_field_name , 1 )
saved_original_material_ids = geometry.GetUserTriangleField(original_material_ids_field_name)
if saved_original_material_ids != None:
saved_original_material_ids_array = Simplygon.spRidArray.SafeCast( saved_original_material_ids )
material_ids = geometry.GetMaterialIds()
saved_original_material_ids_array.DeepCopy(material_ids)
# Save UVs
geometry.AddBaseTypeUserCornerField( Simplygon.EBaseTypes_TYPES_ID_REAL, original_uvs_field_name , 2 )
saved_original_uvs = geometry.GetUserCornerField( original_uvs_field_name )
if saved_original_uvs != None:
saved_original_uvs_array = Simplygon.spRealArray.SafeCast( saved_original_uvs )
original_uvs = geometry.GetTexCoords(0)
saved_original_uvs_array.DeepCopy(original_uvs)
scene_meshes_selection_set = None
scene.GetSelectionSetTable().RemoveItem( scene_meshes_selection_set_id )
def restore_original_material_data(scene, keep_material_id : int, material_map : dict[int, int], original_material_ids_field_name : str, original_uvs_field_name : str):
scene_meshes_selection_set_id = scene.SelectNodes("SceneMesh")
scene_meshes_selection_set = scene.GetSelectionSetTable().GetSelectionSet(scene_meshes_selection_set_id)
# Loop through all meshes in the scene
for node_id in range(scene_meshes_selection_set.GetItemCount()):
scene_mesh = Simplygon.spSceneMesh.SafeCast(scene.GetNodeByGUID( scene_meshes_selection_set.GetItem(node_id)))
geometry = scene_mesh.GetGeometry()
if not geometry:
print("Scene without geometry, skipping")
continue
if not geometry.GetMaterialIds():
print("Geometry with no material IDs, skipping")
continue
if not geometry.GetTexCoords(0):
print("Geometry with no TexCoords, skipping")
continue
material_ids = geometry.GetMaterialIds()
# load original UVs
saved_original_uvs = geometry.GetUserCornerField( original_uvs_field_name )
if saved_original_uvs != None:
saved_original_uvs_array = Simplygon.spRealArray.SafeCast( saved_original_uvs )
current_uvs = geometry.GetTexCoords(0)
for i in range(0, current_uvs.GetItemCount()):
triangle_id = math.floor(i / 2.0 /3.0)
current_material_id = material_ids.GetItem(triangle_id)
if current_material_id == keep_material_id:
current_uvs.SetRealItem(i, saved_original_uvs_array.GetRealItem(i))
# Load saved material IDs
saved_original_material_ids = geometry.GetUserTriangleField( original_material_ids_field_name )
if saved_original_material_ids != None:
saved_original_material_ids_array = Simplygon.spRidArray.SafeCast( saved_original_material_ids )
for triangle_id in range(0, material_ids.GetItemCount()):
original_material_id = saved_original_material_ids_array.GetItem(triangle_id)
current_material_id = material_ids.GetItem(triangle_id)
if current_material_id == keep_material_id:
material_ids.SetItem(triangle_id, material_map[original_material_id])
scene_meshes_selection_set = None
scene.GetSelectionSetTable().RemoveItem( scene_meshes_selection_set_id )
def setup_output_material(material : Simplygon.spMaterial, material_type : MaterialTypes):
"""Set correct settings for output material depending on material type."""
if material_type == MaterialTypes.TRANSPARENT or material_type == MaterialTypes.TATTOO:
material.SetBlendMode(Simplygon.EMaterialBlendMode_Blend)
def get_material_type(scene: Simplygon.spScene, material_id: int) -> MaterialTypes:
"""Return which material type the material is."""
material = scene.GetMaterialTable().GetMaterial(material_id)
material_name = material.GetName().lower()
if "slime" in material_name:
return MaterialTypes.KEEP_ORIGINAL
elif material.IsTransparent():
return MaterialTypes.TRANSPARENT
else:
return MaterialTypes.OPAQUE
def get_material_types(scene : Simplygon.spScene) -> list[MaterialTypes]:
"""Returns a list of all material types in scene."""
material_types = []
for j in range(0, scene.GetMaterialTable().GetMaterialsCount()):
material_type = get_material_type(scene, j)
if not material_type in material_types:
material_types.append(material_type)
return material_types
def get_material_type_index(kind : MaterialTypes, material_kinds : list[MaterialTypes]) -> int:
"""Returs the index in material table of a specific material kind. Used to map MaterialTypes -> integers."""
if not kind in material_kinds:
return -1
return material_kinds.index(kind)
def setup_output_material_settings(output_material : Simplygon.spMappingImageOutputMaterialSettings, material_type : MaterialTypes):
"""Set settings for output material"""
output_material.SetTextureWidth( TEXTURE_SIZE )
output_material.SetTextureHeight( TEXTURE_SIZE )
def cast_channel(sg : Simplygon.ISimplygon, scene : Simplygon.spScene, mapping_image : Simplygon.spMappingImage, channel_name : str, sRGB : bool, output_file_name : str) -> str:
"""Cast material channel to texture file. Returns file name."""
caster = None
# Special case if channel is a normal channel. Then we use a normal caster.
if channel_name == NORMAL_CHANNEL:
caster = sg.CreateNormalCaster()
caster_settings = caster.GetNormalCasterSettings()
caster_settings.SetMaterialChannel(channel_name)
caster_settings.SetOutputImageFileFormat( Simplygon.EImageOutputFormat_PNG)
# Blender specific normal casting settings.
caster_settings.SetGenerateTangentSpaceNormals(True)
caster_settings.SetFlipGreen(False)
caster_settings.SetCalculateBitangentPerFragment(True)
caster_settings.SetNormalizeInterpolatedTangentSpace(False)
# Normal maps are per default non-srgb.
# Avoid normal issues
caster_settings.SetOutputPixelFormat(Simplygon.EPixelFormat_R16G16B16)
else:
caster = sg.CreateColorCaster()
caster_settings = caster.GetColorCasterSettings()
caster_settings.SetMaterialChannel(channel_name)
caster_settings.SetOutputImageFileFormat( Simplygon.EImageOutputFormat_PNG)
caster_settings.SetOutputSRGB(sRGB)
caster.SetMappingImage( mapping_image)
caster.SetSourceMaterials( scene.GetMaterialTable() )
caster.SetSourceTextures( scene.GetTextureTable() )
caster.SetOutputFilePath(output_file_name)
caster.RunProcessing()
return caster.GetOutputFilePath()
def create_texture(sg : Simplygon.ISimplygon, file_path : str, color_space : int) -> Simplygon.spTexture:
"""Create a texture from file_path."""
new_texture = sg.CreateTexture()
new_texture.SetFilePath( file_path )
new_texture.SetName(file_path)
new_texture.SetColorSpace(color_space)
return new_texture
def create_shading_network(sg : Simplygon.ISimplygon, texture_name : str) -> Simplygon.spShadingNode:
"""Create a simple shading network which displays a texture of name texture_name."""
texture_node = sg.CreateShadingTextureNode()
texture_node.SetTexCoordLevel( 0 )
texture_node.SetTextureName( texture_name )
return texture_node
def create_aggregator_processor(sg: Simplygon.ISimplygon, scene: Simplygon.spScene, material_types : list[MaterialTypes]):
"""Create aggregation processor with mapping image set to split materials into different material types."""
# Create the aggregation processor.
aggregation_processor = sg.CreateAggregationProcessor()
aggregation_processor.SetScene( scene )
aggregation_settings = aggregation_processor.GetAggregationSettings()
mapping_image_settings = aggregation_processor.GetMappingImageSettings()
# Merge all geometries into a single geometry.
aggregation_settings.SetMergeGeometries( True )
# Generates a mapping image which is used after the aggregation to cast new materials to the new
# aggregated object.
mapping_image_settings.SetGenerateMappingImage( True )
mapping_image_settings.SetApplyNewMaterialIds( True )
mapping_image_settings.SetGenerateTangents( True )
mapping_image_settings.SetUseFullRetexturing( True )
mapping_image_settings.SetTexCoordGeneratorType( Simplygon.ETexcoordGeneratorType_ChartAggregator )
chart_aggregator_settings = mapping_image_settings.GetChartAggregatorSettings()
chart_aggregator_settings.SetChartAggregatorMode( Simplygon.EChartAggregatorMode_SurfaceArea )
chart_aggregator_settings.SetSeparateOverlappingCharts( False )
# Set input material count to number of materials in input scene.
material_count = scene.GetMaterialTable().GetMaterialsCount()
mapping_image_settings.SetInputMaterialCount( material_count )
# Set output material count to each material kind present in the input scene.
mapping_image_settings.SetOutputMaterialCount( len(material_types))
# Set material mapping where all materials of a certain kind maps to the same output material.
for j in range(0, material_count):
kind = get_material_type(scene,j )
new_id = get_material_type_index(kind, material_types)
mapping_image_settings.GetInputMaterialSettings(j).SetMaterialMapping(new_id)
# Set output material settings.
for j in range(0, len(material_types)):
setup_output_material_settings(mapping_image_settings.GetOutputMaterialSettings(j), material_types[j])
return aggregation_processor
def cast_materials(sg : Simplygon.ISimplygon, scene : Simplygon.spScene, processor : Simplygon.spAggregationProcessor, material_types : list[MaterialTypes], material_table : Simplygon.spMaterialTable, texture_table : Simplygon.spTextureTable):
"""Cast all materials in scene to new texture and material tables."""
for j in range(0, len(material_types)):
print(f"Creating output material for {material_types[j]}...")
# Add new material for each kind
new_material = sg.CreateMaterial()
new_material.SetName(f"{material_types[j].name}")
material_table.AddMaterial( new_material )
if material_types[j] == MaterialTypes.KEEP_ORIGINAL:
continue
for channel in MATERIAL_CHANNELS:
# Cast texture to file for specific mapping image
print(f"Casting {channel[0]}...")
sRGB = channel[1] == Simplygon.EImageColorSpace_sRGB
casted_texture_file = cast_channel(sg, scene, processor.GetMappingImageForImageIndex(j), channel[0], sRGB, f"{TMP_DIR}{channel[0]}_{j}" )
print(f"Casted {casted_texture_file}")
# Create a texture from newly casted texture file.
texture_table.AddTexture(create_texture(sg, casted_texture_file, channel[1]) )
# Create material from texture
new_material.AddMaterialChannel( channel[0] )
new_material.SetShadingNetwork( channel[0], create_shading_network(sg, casted_texture_file) )
setup_output_material(new_material, material_types[j])
def optimize_scene(sg: Simplygon.ISimplygon, scene : Simplygon.spScene):
"""Optimize scene with aggregator."""
material_types = get_material_types(scene)
aggregation_processor = create_aggregator_processor(sg, scene, material_types)
save_original_material_data(scene, ORIGINAL_MATERIAL_IDS_FIELD, ORIGINAL_UVS_FIELD)
# Start the aggregation process.
print("Running aggregator...")
aggregation_processor.RunProcessing()
# Create temporary material and texture tables the output scene will use.
new_material_table = sg.CreateMaterialTable()
new_texture_table = scene.GetTextureTable() #sg.CreateTextureTable()
keep_material_id = get_material_type_index(MaterialTypes.KEEP_ORIGINAL, material_types)
# Cast all materials
cast_materials(sg, scene, aggregation_processor, material_types, new_material_table, new_texture_table)
material_remapping = {}
material_table = scene.GetMaterialTable()
for material_id in range(0, material_table.GetMaterialsCount()):
if get_material_type(scene, material_id) == MaterialTypes.KEEP_ORIGINAL:
material = material_table.GetMaterial(material_id)
new_id = new_material_table.AddItem(material)
material_remapping[material_id] = new_id
# We can not clear texture table above since we still are using it for material casting. Now when all materials are casted we can assign new material table and texture table to scene.
#scene.GetTextureTable().Clear()
scene.GetMaterialTable().Clear()
#scene.GetTextureTable().Copy(new_texture_table)
scene.GetMaterialTable().Copy(new_material_table)
restore_original_material_data(scene, keep_material_id, material_remapping, ORIGINAL_MATERIAL_IDS_FIELD, ORIGINAL_UVS_FIELD)
def process_selection(sg : Simplygon.ISimplygon):
"""Remove and bake decals on selected meshes."""
# Export scene from Blender and import it
scene = export_selection(sg, TMP_DIR+IN_FILE)
# Aggregate optimized scene
optimize_scene(sg, scene)
# Import result into Blender
import_results(sg, scene, TMP_DIR+OUTPUT_FILE)
def main():
sg = simplygon_loader.init_simplygon()
process_selection(sg)
sg = None
gc.collect()
if __name__== "__main__":
main()