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
