I need help finishing a Cinema 4D Script for importing Textures Fast

Post, discuss and share handy resources like textures, models and HDRI maps in this forum.
Forum rules
Please do not post any material that is copyrighted or restricted from public use in any way. OTOY NZ LTD and it's forum members are not liable for any copyright infringements on material in this forum. Please contact us if this is the case and we will remove the material in question.
Post Reply
EMAGICSTUDIOS
Licensed Customer
Posts: 12
Joined: Fri Dec 28, 2018 1:46 pm

I will be adding it to VFX Hut's library for free, but I need some help finishing it. I can't get the displacement to link up, and I want the material to be a universal material for more flexibility. Also it would be ace if it worked across all versions.

Heres the code - any help would be smashing or OTOY feel free to use to integrate :) if its of use



"""
Jack Buck - VFXHUT - 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
}

# 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()
User avatar
bepeg4d
Octane Guru
Posts: 10475
Joined: Wed Jun 02, 2010 6:02 am
Location: Italy
Contact:

Hi,

Please write your request of help in the C4D section of the forum, in this way other C4D users can see it more easily:
viewforum.php?f=87

ciao,
Beppe
Post Reply

Return to “Resources and Sharing”