FAST PBR Script for Cinema 4D & Octane
Posted: Tue May 12, 2026 5:08 pm
So it seems to be working, things id love help with are : for it to default to universal material, and for the displacement to automatically link too with the displacement node in between. a nice to have would be the option to choose if you want Standard surfaces or Octane universal, but not essential. Any help would be super appreciated 
"""
Auto PBR importer for Octane Universal Material in C4D 2025 + Octane 2025.
- Detects PBR maps in a folder by filename.
- Creates an Octane Universal Material and assigns detected maps to the correct channels.
- Improved error handling and ID verification.
"""
import c4d
import os
import re
from c4d import storage
# Octane Plugin IDs - VERIFY THESE FOR YOUR OCTANE VERSION
# To find correct IDs: Create material manually, then print mat.GetType()
OCTANE_IDS = {
"universal_material": 1029501, # Octane Universal Material
"image_texture": 1029508, # Octane Image Texture
"displacement": 1029582, # Octane Displacement node
}
# Material parameter IDs - These need to match your Octane SDK
# Check Octane's documentation or use GetParameter() to find these
# Universal Material uses different parameter names than Standard Surface
OCTANE_PARAMS = {
"diffuse": "OCT_MATERIAL_DIFFUSE_LINK",
"roughness": "OCT_MATERIAL_ROUGHNESS_LINK",
"metallic": "OCT_MATERIAL_METALLIC_LINK",
"normal": "OCT_MATERIAL_NORMAL_LINK",
"emission": "OCT_MATERIAL_EMISSION_LINK",
"opacity": "OCT_MATERIAL_OPACITY_LINK",
"displacement": "OCT_MATERIAL_DISPLACEMENT_LINK",
"ao": "OCT_MATERIAL_AO_LINK",
}
# Keywords to detect map types (order matters - more specific first)
PBR_KEYS = {
"basecolor": ["basecolor", "base_color", "albedo", "diffuse", "diff", "color", "col"],
"roughness": ["roughness", "rough", "rgh"],
"metallic": ["metallic", "metal", "mtl", "metalness"],
"normal": ["normal", "normalmap", "normal_map", "nrm", "norm"],
"height": ["height", "displacement", "disp", "bump"],
"ao": ["ao", "ambientocclusion", "ambient_occlusion", "occlusion"],
"emissive": ["emissive", "emission", "emit", "glow"],
"opacity": ["opacity", "alpha", "transparency", "trans"]
}
IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".tif", ".tiff", ".exr", ".bmp", ".tga", ".hdr"}
def verify_octane_plugin():
"""Check if Octane plugin is loaded and IDs are valid."""
test_mat = c4d.BaseMaterial(OCTANE_IDS["universal_material"])
if not test_mat:
return False, "Octane Universal Material ID not found. Plugin may not be loaded."
test_shader = c4d.BaseShader(OCTANE_IDS["image_texture"])
if not test_shader:
return False, "Octane Image Texture ID not found."
return True, "Octane plugin verified successfully."
def find_maps(folder):
"""
Scan folder for PBR texture maps based on filename keywords.
Returns dict of map_type: filepath.
"""
if not os.path.isdir(folder):
return {}
found = {}
duplicates = {}
try:
files = os.listdir(folder)
except Exception as e:
print(f"Error reading folder: {e}")
return {}
for fname in files:
ext = os.path.splitext(fname)[1].lower()
if ext not in IMAGE_EXTS:
continue
name_lower = fname.lower()
# Check each map type
for map_type, keywords in PBR_KEYS.items():
matched = False
for kw in keywords:
# Pattern matches keyword with word boundaries or common delimiters
pattern = r"(?:^|[_\-. ])" + re.escape(kw) + r"(?:$|[_\-. ])"
if re.search(pattern, name_lower):
filepath = os.path.join(folder, fname)
if map_type not in found:
found[map_type] = filepath
print(f"✓ Found {map_type}: {fname}")
else:
# Track duplicates
if map_type not in duplicates:
duplicates[map_type] = [found[map_type]]
duplicates[map_type].append(filepath)
print(f"⚠ Duplicate {map_type} ignored: {fname}")
matched = True
break
if matched:
break # Don't check other map types for this file
if duplicates:
print(f"\nWarning: Found duplicate maps for: {', '.join(duplicates.keys())}")
print("Only the first match for each type was used.")
return found
def create_octane_image_shader(filepath):
"""Create an Octane ImageTexture shader with the given file path."""
try:
shader = c4d.BaseShader(OCTANE_IDS["image_texture"])
if not shader:
print(f"Failed to create Octane Image Texture shader")
return None
# Try to set the file path - parameter ID might vary
# Common parameter IDs for image path in Octane
possible_params = [
c4d.IMAGETEXTURE_FILE,
1000, # Common Octane file parameter
]
success = False
for param_id in possible_params:
try:
shader[param_id] = filepath
success = True
break
except:
continue
if not success:
print(f"Warning: Could not set file path for shader. Manual assignment may be needed.")
return shader
except Exception as e:
print(f"Error creating image shader: {e}")
return None
def link_shader_to_material(mat, doc, map_type, shader):
"""
Insert shader into material and link to appropriate channel.
Returns True if successful.
"""
if not shader:
return False
# Get parameter name from mapping
param_name = OCTANE_PARAMS.get(map_type)
if not param_name:
print(f"No parameter mapping for map type: {map_type}")
return False
# Check if parameter constant exists in c4d module
if not hasattr(c4d, param_name):
print(f"Warning: Parameter constant '{param_name}' not found in c4d module.")
print(f"You may need to update OCTANE_PARAMS with correct parameter IDs.")
return False
param_id = getattr(c4d, param_name)
try:
# Insert shader into material
mat.InsertShader(shader)
doc.AddUndo(c4d.UNDOTYPE_NEW, shader)
# Assign shader to material parameter
mat[param_id] = shader
doc.AddUndo(c4d.UNDOTYPE_CHANGE, mat)
print(f" → Linked {map_type} shader successfully")
return True
except Exception as e:
print(f" ✗ Failed to link {map_type} shader: {e}")
return False
def create_octane_material(doc, maps, material_name="Auto_Octane_PBR"):
"""
Creates Octane Universal Material and assigns detected PBR maps.
Returns the created material or None if failed.
"""
try:
mat = c4d.BaseMaterial(OCTANE_IDS["universal_material"])
if not mat:
print("Failed to create Octane Universal Material")
return None
except Exception as e:
print(f"Error creating material: {e}")
return None
mat.SetName(material_name)
doc.InsertMaterial(mat)
doc.AddUndo(c4d.UNDOTYPE_NEW, mat)
print(f"\nCreating material: {material_name}")
# Map PBR types to parameter keys
map_to_param = {
"basecolor": "diffuse",
"roughness": "roughness",
"metallic": "metallic",
"normal": "normal",
"emissive": "emission",
"opacity": "opacity",
"height": "displacement",
"ao": "ao",
}
success_count = 0
# Link each detected map
for map_type, filepath in maps.items():
param_key = map_to_param.get(map_type)
if not param_key:
continue
shader = create_octane_image_shader(filepath)
if shader and link_shader_to_material(mat, doc, param_key, shader):
success_count += 1
# Update material
mat.Message(c4d.MSG_UPDATE)
mat.Update(True, True)
print(f"\n✓ Material created with {success_count}/{len(maps)} maps linked successfully")
return mat
def main():
"""Main execution function."""
doc = c4d.documents.GetActiveDocument()
# Verify Octane plugin
print("=" * 60)
print("Octane PBR Auto-Importer")
print("=" * 60)
verified, msg = verify_octane_plugin()
print(f"\n{msg}\n")
if not verified:
c4d.gui.MessageDialog(
f"Octane plugin verification failed!\n\n{msg}\n\n"
"Please ensure Octane is installed and check the plugin IDs in the script."
)
return
# Select folder
folder = storage.LoadDialog(
title="Select Folder with PBR Maps",
type=c4d.FILESELECTTYPE_ANYTHING,
flags=c4d.FILESELECT_DIRECTORY
)
if not folder:
print("No folder selected. Operation cancelled.")
return
print(f"\nScanning folder: {folder}\n")
# Find PBR maps
maps = find_maps(folder)
if not maps:
msg = "No recognizable PBR maps found in the selected folder.\n\n" \
"Expected map types:\n" \
"- Base Color / Albedo / Diffuse\n" \
"- Roughness\n" \
"- Metallic / Metal\n" \
"- Normal\n" \
"- Height / Displacement\n" \
"- Ambient Occlusion (AO)\n" \
"- Emissive / Emission\n" \
"- Opacity / Alpha"
c4d.gui.MessageDialog(msg)
print("\n" + msg)
return
# Extract folder name for material naming
folder_name = os.path.basename(folder.rstrip(os.sep))
material_name = f"Octane_{folder_name}" if folder_name else "Octane_PBR_Material"
# Create material with undo support
doc.StartUndo()
mat = create_octane_material(doc, maps, material_name)
doc.EndUndo()
if mat:
c4d.gui.MessageDialog(
f"✓ Octane material created successfully!\n\n"
f"Material: {material_name}\n"
f"Maps loaded: {len(maps)}\n\n"
f"Check the console for details."
)
else:
c4d.gui.MessageDialog(
"Failed to create Octane material.\n\n"
"Check the console for error details.\n"
"You may need to verify plugin IDs and parameter constants."
)
c4d.EventAdd()
print("=" * 60)
if __name__ == "__main__":
main()
"""
Auto PBR importer for Octane Universal Material in C4D 2025 + Octane 2025.
- Detects PBR maps in a folder by filename.
- Creates an Octane Universal Material and assigns detected maps to the correct channels.
- Improved error handling and ID verification.
"""
import c4d
import os
import re
from c4d import storage
# Octane Plugin IDs - VERIFY THESE FOR YOUR OCTANE VERSION
# To find correct IDs: Create material manually, then print mat.GetType()
OCTANE_IDS = {
"universal_material": 1029501, # Octane Universal Material
"image_texture": 1029508, # Octane Image Texture
"displacement": 1029582, # Octane Displacement node
}
# Material parameter IDs - These need to match your Octane SDK
# Check Octane's documentation or use GetParameter() to find these
# Universal Material uses different parameter names than Standard Surface
OCTANE_PARAMS = {
"diffuse": "OCT_MATERIAL_DIFFUSE_LINK",
"roughness": "OCT_MATERIAL_ROUGHNESS_LINK",
"metallic": "OCT_MATERIAL_METALLIC_LINK",
"normal": "OCT_MATERIAL_NORMAL_LINK",
"emission": "OCT_MATERIAL_EMISSION_LINK",
"opacity": "OCT_MATERIAL_OPACITY_LINK",
"displacement": "OCT_MATERIAL_DISPLACEMENT_LINK",
"ao": "OCT_MATERIAL_AO_LINK",
}
# Keywords to detect map types (order matters - more specific first)
PBR_KEYS = {
"basecolor": ["basecolor", "base_color", "albedo", "diffuse", "diff", "color", "col"],
"roughness": ["roughness", "rough", "rgh"],
"metallic": ["metallic", "metal", "mtl", "metalness"],
"normal": ["normal", "normalmap", "normal_map", "nrm", "norm"],
"height": ["height", "displacement", "disp", "bump"],
"ao": ["ao", "ambientocclusion", "ambient_occlusion", "occlusion"],
"emissive": ["emissive", "emission", "emit", "glow"],
"opacity": ["opacity", "alpha", "transparency", "trans"]
}
IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".tif", ".tiff", ".exr", ".bmp", ".tga", ".hdr"}
def verify_octane_plugin():
"""Check if Octane plugin is loaded and IDs are valid."""
test_mat = c4d.BaseMaterial(OCTANE_IDS["universal_material"])
if not test_mat:
return False, "Octane Universal Material ID not found. Plugin may not be loaded."
test_shader = c4d.BaseShader(OCTANE_IDS["image_texture"])
if not test_shader:
return False, "Octane Image Texture ID not found."
return True, "Octane plugin verified successfully."
def find_maps(folder):
"""
Scan folder for PBR texture maps based on filename keywords.
Returns dict of map_type: filepath.
"""
if not os.path.isdir(folder):
return {}
found = {}
duplicates = {}
try:
files = os.listdir(folder)
except Exception as e:
print(f"Error reading folder: {e}")
return {}
for fname in files:
ext = os.path.splitext(fname)[1].lower()
if ext not in IMAGE_EXTS:
continue
name_lower = fname.lower()
# Check each map type
for map_type, keywords in PBR_KEYS.items():
matched = False
for kw in keywords:
# Pattern matches keyword with word boundaries or common delimiters
pattern = r"(?:^|[_\-. ])" + re.escape(kw) + r"(?:$|[_\-. ])"
if re.search(pattern, name_lower):
filepath = os.path.join(folder, fname)
if map_type not in found:
found[map_type] = filepath
print(f"✓ Found {map_type}: {fname}")
else:
# Track duplicates
if map_type not in duplicates:
duplicates[map_type] = [found[map_type]]
duplicates[map_type].append(filepath)
print(f"⚠ Duplicate {map_type} ignored: {fname}")
matched = True
break
if matched:
break # Don't check other map types for this file
if duplicates:
print(f"\nWarning: Found duplicate maps for: {', '.join(duplicates.keys())}")
print("Only the first match for each type was used.")
return found
def create_octane_image_shader(filepath):
"""Create an Octane ImageTexture shader with the given file path."""
try:
shader = c4d.BaseShader(OCTANE_IDS["image_texture"])
if not shader:
print(f"Failed to create Octane Image Texture shader")
return None
# Try to set the file path - parameter ID might vary
# Common parameter IDs for image path in Octane
possible_params = [
c4d.IMAGETEXTURE_FILE,
1000, # Common Octane file parameter
]
success = False
for param_id in possible_params:
try:
shader[param_id] = filepath
success = True
break
except:
continue
if not success:
print(f"Warning: Could not set file path for shader. Manual assignment may be needed.")
return shader
except Exception as e:
print(f"Error creating image shader: {e}")
return None
def link_shader_to_material(mat, doc, map_type, shader):
"""
Insert shader into material and link to appropriate channel.
Returns True if successful.
"""
if not shader:
return False
# Get parameter name from mapping
param_name = OCTANE_PARAMS.get(map_type)
if not param_name:
print(f"No parameter mapping for map type: {map_type}")
return False
# Check if parameter constant exists in c4d module
if not hasattr(c4d, param_name):
print(f"Warning: Parameter constant '{param_name}' not found in c4d module.")
print(f"You may need to update OCTANE_PARAMS with correct parameter IDs.")
return False
param_id = getattr(c4d, param_name)
try:
# Insert shader into material
mat.InsertShader(shader)
doc.AddUndo(c4d.UNDOTYPE_NEW, shader)
# Assign shader to material parameter
mat[param_id] = shader
doc.AddUndo(c4d.UNDOTYPE_CHANGE, mat)
print(f" → Linked {map_type} shader successfully")
return True
except Exception as e:
print(f" ✗ Failed to link {map_type} shader: {e}")
return False
def create_octane_material(doc, maps, material_name="Auto_Octane_PBR"):
"""
Creates Octane Universal Material and assigns detected PBR maps.
Returns the created material or None if failed.
"""
try:
mat = c4d.BaseMaterial(OCTANE_IDS["universal_material"])
if not mat:
print("Failed to create Octane Universal Material")
return None
except Exception as e:
print(f"Error creating material: {e}")
return None
mat.SetName(material_name)
doc.InsertMaterial(mat)
doc.AddUndo(c4d.UNDOTYPE_NEW, mat)
print(f"\nCreating material: {material_name}")
# Map PBR types to parameter keys
map_to_param = {
"basecolor": "diffuse",
"roughness": "roughness",
"metallic": "metallic",
"normal": "normal",
"emissive": "emission",
"opacity": "opacity",
"height": "displacement",
"ao": "ao",
}
success_count = 0
# Link each detected map
for map_type, filepath in maps.items():
param_key = map_to_param.get(map_type)
if not param_key:
continue
shader = create_octane_image_shader(filepath)
if shader and link_shader_to_material(mat, doc, param_key, shader):
success_count += 1
# Update material
mat.Message(c4d.MSG_UPDATE)
mat.Update(True, True)
print(f"\n✓ Material created with {success_count}/{len(maps)} maps linked successfully")
return mat
def main():
"""Main execution function."""
doc = c4d.documents.GetActiveDocument()
# Verify Octane plugin
print("=" * 60)
print("Octane PBR Auto-Importer")
print("=" * 60)
verified, msg = verify_octane_plugin()
print(f"\n{msg}\n")
if not verified:
c4d.gui.MessageDialog(
f"Octane plugin verification failed!\n\n{msg}\n\n"
"Please ensure Octane is installed and check the plugin IDs in the script."
)
return
# Select folder
folder = storage.LoadDialog(
title="Select Folder with PBR Maps",
type=c4d.FILESELECTTYPE_ANYTHING,
flags=c4d.FILESELECT_DIRECTORY
)
if not folder:
print("No folder selected. Operation cancelled.")
return
print(f"\nScanning folder: {folder}\n")
# Find PBR maps
maps = find_maps(folder)
if not maps:
msg = "No recognizable PBR maps found in the selected folder.\n\n" \
"Expected map types:\n" \
"- Base Color / Albedo / Diffuse\n" \
"- Roughness\n" \
"- Metallic / Metal\n" \
"- Normal\n" \
"- Height / Displacement\n" \
"- Ambient Occlusion (AO)\n" \
"- Emissive / Emission\n" \
"- Opacity / Alpha"
c4d.gui.MessageDialog(msg)
print("\n" + msg)
return
# Extract folder name for material naming
folder_name = os.path.basename(folder.rstrip(os.sep))
material_name = f"Octane_{folder_name}" if folder_name else "Octane_PBR_Material"
# Create material with undo support
doc.StartUndo()
mat = create_octane_material(doc, maps, material_name)
doc.EndUndo()
if mat:
c4d.gui.MessageDialog(
f"✓ Octane material created successfully!\n\n"
f"Material: {material_name}\n"
f"Maps loaded: {len(maps)}\n\n"
f"Check the console for details."
)
else:
c4d.gui.MessageDialog(
"Failed to create Octane material.\n\n"
"Check the console for error details.\n"
"You may need to verify plugin IDs and parameter constants."
)
c4d.EventAdd()
print("=" * 60)
if __name__ == "__main__":
main()