Hi,
I'm working on a code to copy take overrides from one scene to another. One script captures details from each take in my reference scene and saves them as a JSON file. Another script loads the JSON and compares all takes to identify differences in names, hierarchy, applied materials, transforms, visibility, etc.
I want the script to both print the differences between the captured JSON data and the current scene and automatically apply the take-specific changes. I assume the take hierarchy, scene objects, and materials are identical (which I verify elsewhere).
I can capture many details but struggle to compare takes with the JSON data and restore them correctly. I also suspect my code is unnecessarily long and complex for such a simple task. Could you review both my capture and restore scripts and help me get them working efficiently?
I've attached my test scenes and scripts.
Thank you in advance.
Best regards,
Tomasz
#GrabReferenceSnapshot.py
import c4d
import collections
import maxon
from c4d import utils
import json
import os
def create_snapshot(doc, filepath):
def return_full_path_per_object(op, parent_mesh_object_name=""):
if not op:
return ""
path_parts = []
current = op
# Build path from current object up to root
while current:
name = current.GetName()
if name == parent_mesh_object_name:
break
path_parts.insert(0, name)
current = current.GetUp()
# Join path parts with forward slashes
return "/".join(path_parts)
def serialize_descid(desc_id):
"""Converts a c4d.DescID to a serializable format."""
return [desc_id[i].id for i in range(desc_id.GetDepth())]
try:
# Ensure the main take is active
take_data = doc.GetTakeData()
if take_data is None:
raise RuntimeError("No take data in the document.")
main_take = take_data.GetMainTake()
take_data.SetCurrentTake(main_take)
# Initialize the snapshot dictionary
snapshot = {
'takes': {}
}
# Get takes information
take_data = doc.GetTakeData()
if take_data:
main_take = take_data.GetMainTake()
def process_take_changes(take):
"""Gets the overrides for a specific take."""
if take == main_take:
return {"name": take.GetName(), "overrides": []} # Skip overrides for main take
take_data.SetCurrentTake(take)
doc = c4d.documents.GetActiveDocument()
doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_0)
take_changes = {
"name": take.GetName(),
"overrides": []
}
# Get only the overrides for this take
override_data = take.GetOverrides()
if override_data:
for override in override_data:
override_obj = override.GetSceneNode()
if not override_obj:
continue
# Log the object being processed
print(f"Processing override for object: {override_obj.GetName()}")
# Use the material name as a unique identifier
material_name = override_obj.GetName() if isinstance(override_obj, c4d.BaseMaterial) else None
overridden_params = override.GetAllOverrideDescID()
for desc_id in overridden_params:
description = override_obj.GetDescription(c4d.DESCFLAGS_DESC_NONE)
if description:
parameter_container = description.GetParameter(desc_id, None)
if parameter_container:
param_name = parameter_container[c4d.DESC_NAME]
value = override.GetParameter(desc_id, c4d.DESCFLAGS_GET_0)
# Use return_full_path_per_object to get the object path
print (f"in take{take.GetName()} checking object path for {override_obj.GetName()}")
object_path = return_full_path_per_object(override_obj, "")
if "/" in object_path:
print (f"object_path: {object_path}")
take_changes["overrides"].append({
"object_name": override_obj.GetName(),
"object_path": object_path,
"parameter": param_name,
"parameter_id": serialize_descid(desc_id),
"value": str(value) if not isinstance(value, (int, float, bool, str)) else value,
"material_name": material_name # Add material name for comparison
})
print(f"Captured override: {param_name} = {value} for object {object_path}")
return take_changes
def process_take(take):
if not take:
return
# Store only serializable data
take_info = {
'name': take.GetName(),
'parent': take.GetUp().GetName() if take.GetUp() else None,
'changes': process_take_changes(take) # Capture take changes
}
# Store in snapshot using take name as key
snapshot['takes'][take.GetName()] = take_info
# Process child takes
child = take.GetDown()
while child:
process_take(child)
child = child.GetNext()
process_take(main_take)
# Save the snapshot to JSON file
try:
# Create all necessary directories
os.makedirs(os.path.dirname(filepath), exist_ok=True)
# Save the file
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(snapshot, f, indent=4)
return True
except Exception as e:
print(f"Error saving snapshot file: {str(e)}")
return False
except Exception as e:
print(f"Error creating snapshot: {str(e)}")
return False
def main():
doc = c4d.documents.GetActiveDocument()
# Get both the document name and path
doc_name = doc.GetDocumentName()
doc_path = doc.GetDocumentPath()
full_path = os.path.join(doc_path, doc_name)
print("full_path: " + str(full_path))
project_folder = os.path.dirname(full_path)
print("project_folder : " + str(project_folder))
project_name = os.path.basename(project_folder)
print("project_name : " + str(project_name))
# Construct final path
final_path = os.path.join(project_folder, "snapshots", project_name + "_final.json")
print("\nPath verification:")
print(f"Final snapshot path: {final_path}")
print("\nAttempting to create final snapshot...")
try:
result = create_snapshot(doc, final_path)
print(f"create_snapshot call completed. Result: {result}")
except Exception as e:
print(f"Error in create_snapshot: {str(e)}")
import traceback
print(f"Traceback:\n{traceback.format_exc()}")
if __name__ == "__main__":
c4d.CallCommand(13957) # Clear Console
main()
#RestoreSnapshot
import c4d
import collections
import maxon
from c4d import utils
import os
import c4d
import json
def return_full_path_per_object(op, parent_mesh_object_name=""):
if not op:
return ""
path_parts = []
current = op
# Build path from current object up to root
while current:
name = current.GetName()
if name == parent_mesh_object_name:
break
path_parts.insert(0, name)
current = current.GetUp()
# Join path parts with forward slashes
return "/".join(path_parts)
def serialize_descid(desc_id):
"""Converts a c4d.DescID to a serializable format."""
return [desc_id[i].id for i in range(desc_id.GetDepth())]
def check_takes(doc, snapshot):
try:
print("\nPerforming takes check...")
take_data = doc.GetTakeData()
if take_data is None:
raise RuntimeError("No take data in the document.")
main_take = take_data.GetMainTake()
snapshot_takes = snapshot.get('takes', {})
def get_take_overrides(take):
"""Gets the overrides for a specific take."""
if take == main_take:
return [] # Skip overrides for main take
# Activate the take before checking its overrides
take_data.SetCurrentTake(take)
doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_0)
override_data = take.GetOverrides()
overrides = []
if override_data:
for override in override_data:
override_obj = override.GetSceneNode()
if not override_obj:
continue
overridden_params = override.GetAllOverrideDescID()
for desc_id in overridden_params:
description = override_obj.GetDescription(c4d.DESCFLAGS_DESC_NONE)
if description:
parameter_container = description.GetParameter(desc_id, None)
if parameter_container:
param_name = parameter_container[c4d.DESC_NAME]
value = override.GetParameter(desc_id, c4d.DESCFLAGS_GET_0)
# Get object path using the helper function
object_path = return_full_path_per_object(override_obj)
# Use the material name or another unique identifier
material_name = override_obj.GetName() if isinstance(override_obj, c4d.BaseMaterial) else None
overrides.append({
"object_name": override_obj.GetName(),
"object_path": object_path,
"parameter": param_name,
"parameter_id": str(desc_id), # Convert DescID to string for comparison
"value": str(value) if not isinstance(value, (int, float, bool, str)) else value,
"material_name": material_name # Add material name for comparison
})
return overrides
def compare_takes(current_take, snapshot_take_data):
# Debugging: Print snapshot_take_data to verify its structure
print(f"Snapshot data for take '{current_take.GetName()}': {snapshot_take_data}")
# Ensure 'overrides' key exists in snapshot_take_data
if 'overrides' not in snapshot_take_data:
print(f"Error: 'overrides' key not found in snapshot data for take '{current_take.GetName()}'")
return False
# Initialize current_overrides with the current scene's override data
current_overrides = [] # Ensure this is defined before use
# Retrieve the current overrides for the take
override_data = current_take.GetOverrides()
if override_data:
for override in override_data:
override_obj = override.GetSceneNode()
if not override_obj:
continue
# Use the material name as a unique identifier
material_name = override_obj.GetName() if isinstance(override_obj, c4d.BaseMaterial) else None
overridden_params = override.GetAllOverrideDescID()
for desc_id in overridden_params:
description = override_obj.GetDescription(c4d.DESCFLAGS_DESC_NONE)
if description:
parameter_container = description.GetParameter(desc_id, None)
if parameter_container:
param_name = parameter_container[c4d.DESC_NAME]
value = override.GetParameter(desc_id, c4d.DESCFLAGS_GET_0)
# Use return_full_path_per_object to get the object path
object_path = return_full_path_per_object(override_obj, "")
current_overrides.append({
"object_name": override_obj.GetName(),
"object_path": object_path,
"parameter": param_name,
"parameter_id": serialize_descid(desc_id),
"value": str(value) if not isinstance(value, (int, float, bool, str)) else value,
"material_name": material_name # Add material name for comparison
})
# Now compare current_overrides with snapshot_take_data['overrides']
snapshot_overrides = snapshot_take_data['overrides']
for curr_override, snap_override in zip(current_overrides, snapshot_overrides):
# Compare using material names if available
if curr_override.get('material_name') != snap_override.get('material_name'):
print(f" Material name mismatch in take '{current_take.GetName()}':")
print(f" Expected: {snap_override.get('material_name')}")
print(f" Found: {curr_override.get('material_name')}")
return False
# Compare other attributes
if (curr_override['object_path'] != snap_override['object_path'] or
curr_override['parameter'] != snap_override['parameter'] or
curr_override['value'] != snap_override['value']):
print(f" Override mismatch in take '{current_take.GetName()}':")
print(f" Object: {curr_override['object_path']}")
print(f" Parameter: {curr_override['parameter']}")
print(f" Expected: {snap_override['value']}")
print(f" Found: {curr_override['value']}")
return False
return True
# Start comparison with main take
all_match = True
def process_take(take):
nonlocal all_match
if not take:
return
take_name = take.GetName()
if take_name not in snapshot_takes:
print(f"\nTake not found in snapshot: {take_name}")
all_match = False
return
# Compare current take with snapshot
if not compare_takes(take, snapshot_takes[take_name]):
all_match = False
# Process child takes
child = take.GetDown()
while child:
process_take(child)
child = child.GetNext()
try:
# Start with main take
process_take(main_take)
# Restore main take at the end
take_data.SetCurrentTake(main_take)
doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_0)
if all_match:
print("\nAll takes match.")
else:
print("\nTakes check failed.")
return all_match
except Exception as e:
print(f"Error during take comparison: {str(e)}")
# Ensure main take is restored even if an error occurs
take_data.SetCurrentTake(main_take)
doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_0)
return False
except Exception as e:
print(f"Error checking takes: {str(e)}")
return False
def restore_snapshot(doc, snapshot_path, loose_comparison=False):
try:
print("Loading snapshot file...")
# Load the snapshot file
with open(snapshot_path, 'r', encoding='utf-8') as f:
snapshot = json.load(f)
# Ensure the main take is active
take_data = doc.GetTakeData()
if take_data is None:
raise RuntimeError("No take data in the document.")
main_take = take_data.GetMainTake()
take_data.SetCurrentTake(main_take)
# Check takes
if not check_takes(doc, snapshot):
return False
print("Snapshot restoration check completed successfully.")
return True
except Exception as e:
print(f"Error checking snapshot: {str(e)}")
return False
def main():
doc = c4d.documents.GetActiveDocument()
# Get both the document name and path
doc_name = doc.GetDocumentName()
doc_path = doc.GetDocumentPath()
full_path = os.path.join(doc_path, doc_name)
print("full_path: " + str(full_path))
project_folder = os.path.dirname(full_path)
print("project_folder : " + str(project_folder))
project_name = os.path.basename(project_folder)
print("project_name : " + str(project_name))
# Construct path to the snapshot we want to restore
snapshot_path = os.path.join(project_folder, "snapshots", project_name + "_final.json")
print("\nPath verification:")
print(f"Snapshot path to restore: {snapshot_path}")
# Verify the snapshot file exists
if not os.path.exists(snapshot_path):
print(f"Error: Snapshot file not found at {snapshot_path}")
return
print("\nAttempting to restore snapshot...")
try:
result = restore_snapshot(doc, snapshot_path)
print(f"restore_snapshot call completed. Result: {result}")
except Exception as e:
print(f"Error in restore_snapshot: {str(e)}")
import traceback
print(f"Traceback:\n{traceback.format_exc()}")
if __name__ == "__main__":
c4d.CallCommand(13957) # Clear Console
main()
Scenes for testing:
SimpleScene4Reference.c4d
SimpleScene4Restore.c4d