Page 1 of 1

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

Posted: Sat Nov 29, 2025 10:00 am
by EMAGICSTUDIOS
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()

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

Posted: Sat Nov 29, 2025 2:49 pm
by bepeg4d
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