From 78e90cab10f9061ed9ad29a24c706ad2abbe6a7a Mon Sep 17 00:00:00 2001 From: montish Date: Sat, 13 May 2023 15:08:32 -0700 Subject: [PATCH 1/4] added code to output clean for webui --- README.md | 1 + export_diffusion.py | 415 +++++++++++++++++++++++++++++++------------- 2 files changed, 297 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index 010ddd9..b1a9ac4 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Download/clone the repo, or just the file `export_diffusion.py`. In Blender go t * Start Frame - Which frame to start from, inclusive. Note: the first frame will always be exporteds as frame 0 * End Frame - Which frame to stop at, exclusive. * Translation Scale -- How much to multiply blender world units by for the translate x,y,z values. +* Added export raw data - This option removes the clutter for ease of use copy/paste into webui. Working on webui import and parseq import. # Usage Notes After exporting, open the text file. All exported cameras will have their own block of strings. The strings are designed as python code you can copy/paste as a block into the notebook animation section. diff --git a/export_diffusion.py b/export_diffusion.py index 1b64990..0e03df5 100644 --- a/export_diffusion.py +++ b/export_diffusion.py @@ -1,10 +1,10 @@ bl_info = { - "name": "Export Camera Animation to Diffusion Notebook String", - "author": "Michael Walker (@mwalk10)", - "version": (1, 1, 4), - "blender": (3, 3, 0), + "name": "Export Camera Animation to Deforum collab/webui", + "author": "Michael Walker (@mwalk10) / Kewk (@KewkD)", + "version": (1, 2, 0), + "blender": (3, 5, 1), "location": "File > Export > Diffusion Notebook String", - "description": "Export camera animations formatted for use in Deforum diffusion collab notebook animations.", + "description": "Export camera animations formatted for use in Deforum: collab and Webui's", "warning": "", "wiki_url": "", "category": "Import-Export", @@ -12,100 +12,126 @@ import bpy +from bpy_extras.io_utils import ImportHelper from math import degrees -from mathutils import Vector -from bpy import context from math import isclose import json +import re +# Define a function to round small numbers to zero, with a customisable threshold def roundZero(num, magnitude_thresh = 0.00001): + # If the absolute value of the number is greater than the threshold if abs(num) > magnitude_thresh: + # Return the original number return num else: + # Otherwise, return 0 return 0 - + +# Define a function to convert an array into a string of keyframes def arr_to_keyframes(arr): + # Initialize an empty string to store the keyframes keyframes = "" + # Enumerate over the array (i will be the index, val will be the value at that index) for i, val in enumerate(arr): + # Round the current value to zero if it's smaller than the threshold val = roundZero(val) - #if we previously had a zero, then we can stop emitting zeroes until right before the next nonzero + # Check if the current value is close to the previous one last_is_same = i > 0 and isclose(val, roundZero(arr[i-1])) - next_is_same = (i+1) < len(arr) and isclose(val, roundZero(arr[i+1])) - - omit = last_is_same and next_is_same - + # Check if the current value is close to the next one + next_is_same = (i+1) < len(arr) and isclose(val, roundZero(arr[i+1])) + # Determine if the current value should be omitted (it's the same as both the previous and next values) + omit = last_is_same and next_is_same + # If the current value shouldn't be omitted if not omit: + # Add it to the keyframes string, formatted as "index:(value)," keyframes += f"{i}:({val})," + # Return the keyframes string return keyframes + -def cameras_to_string(context, startFrame, endFrame, cameras, translation_scale = 50, output_camcode = True, output_json = False,): - # get the current selection +# This function transforms camera data to a string representation. +def cameras_to_string(context, startFrame, endFrame, cameras, translation_scale = 50, output_camcode = True, output_json = False, output_raw_frames = False): + # Get the current scene scene = context.scene + # Save the current frame currentFrame = scene.frame_current - + + # If no cameras are selected, print a message and return if len(cameras) == 0: print("Nothing selected!") return "No Cameras selected for export" - + + # Initialize an empty string for the export export_string = "" - # iterate through the selected objects + # Loop over all selected cameras for sel in cameras: - #init + # Set the scene frame to the start frame scene.frame_set(startFrame) - + + # Initialize lists to store translation and rotation values translation_x = [] translation_y = [] translation_z = [] rotation_3d_x = [] rotation_3d_y = [] rotation_3d_z = [] - + + # Save the current camera's world matrix and rotation oldMat = sel.matrix_world.copy() oldRot = oldMat.to_quaternion() - - #cycle trough all the animated frames + + # Loop over all frames from start to end for frame in range(startFrame+1, endFrame): - #Update animation frame to grab values from + # Set the scene frame to the current frame scene.frame_set(frame) - - newMat = sel.matrix_world.copy() #local to world matrix + + # Save the current camera's world matrix and rotation + newMat = sel.matrix_world.copy() newRot = newMat.to_quaternion() - + + # Get the inverse of the new world matrix and its rotation worldToLocal = newMat.inverted() wlRot = worldToLocal.to_quaternion() - + + # Calculate the difference in position between the old and new frames posDiff = newMat.to_translation() - oldMat.to_translation() posDiffLocal = wlRot @ posDiff - + + # Store the scaled translation values translation_x.append(translation_scale*posDiffLocal.x) translation_y.append(translation_scale*posDiffLocal.y) translation_z.append(-translation_scale*posDiffLocal.z) - + + # Calculate the difference in rotation between the old and new frames rotDiff = oldRot.rotation_difference(newRot).to_euler("XYZ") - + + # Store the rotation values in degrees rotation_3d_x.append(degrees(rotDiff.x)) rotation_3d_y.append(degrees(-rotDiff.y)) rotation_3d_z.append(degrees(-rotDiff.z)) - + + # Update the old matrix and rotation for the next frame oldMat = newMat oldRot = newRot - - #Done looping over frames, now to format for print - export_string += f"\nCamera Export: {sel.name}\n" - - - export_string += f'translation_x = "{arr_to_keyframes(translation_x)}" #@param {{type:"string"}}\n' - export_string += f'translation_y = "{arr_to_keyframes(translation_y)}" #@param {{type:"string"}}\n' - export_string += f'translation_z = "{arr_to_keyframes(translation_z)}" #@param {{type:"string"}}\n' - export_string += f'rotation_3d_x = "{arr_to_keyframes(rotation_3d_x)}" #@param {{type:"string"}}\n' - export_string += f'rotation_3d_y = "{arr_to_keyframes(rotation_3d_y)}" #@param {{type:"string"}}\n' - export_string += f'rotation_3d_z = "{arr_to_keyframes(rotation_3d_z)}" #@param {{type:"string"}}\n' - + + # Check if raw frames should be output + if not output_raw_frames: + # Add camera export data to the export string + export_string += f"\nCamera Export: {sel.name}\n" + for var, label in zip([translation_x, translation_y, translation_z, rotation_3d_x, rotation_3d_y, rotation_3d_z], + ['translation_x', 'translation_y', 'translation_z', 'rotation_3d_x', 'rotation_3d_y', 'rotation_3d_z']): + export_string += f'{label} = "{arr_to_keyframes(var)}" #@param {{type:"string"}}\n' + + # Check if camera code should be output if output_camcode: + # Add camera code to the export string export_string += f'cam_code:\n(translation_x,translation_y,translation_z,rotation_3d_x,rotation_3d_y,rotation_3d_z) = ("{arr_to_keyframes(translation_x)}", "{arr_to_keyframes(translation_y)}", "{arr_to_keyframes(translation_z)}", "{arr_to_keyframes(rotation_3d_x)}", "{arr_to_keyframes(rotation_3d_y)}", "{arr_to_keyframes(rotation_3d_z)}")\n' + # Check if JSON data should be output if output_json: + # Create a dictionary with all the camera data jsondict = { "translation_x" : translation_x, "translation_y" : translation_y, @@ -113,61 +139,116 @@ def cameras_to_string(context, startFrame, endFrame, cameras, translation_scale "rotation_3d_x" : rotation_3d_x, "rotation_3d_y" : rotation_3d_y, "rotation_3d_z" : rotation_3d_z} + + # Add the JSON representation of the dictionary to the export string export_string += f"JSON:\n {json.dumps(jsondict)}\n" - + + # Check if raw frame data should be output + if output_raw_frames: + # Create a dictionary with all the raw frame data + raw_frames = { + "translation_x": translation_x, + "translation_y": translation_y, + "translation_z": translation_z, + "rotation_3d_x": rotation_3d_x, + "rotation_3d_y": rotation_3d_y, + "rotation_3d_z": rotation_3d_z + } + + # Loop over all items in the dictionary + for key, arr in raw_frames.items(): + # Initialize the string for raw frames + raw_frame_str = "" + last_val = None + for i, val in enumerate(arr): + # Check if the value has changed from the last frame + if last_val is None or not isclose(val, last_val): + # Add the frame index and value to the raw frame string + raw_frame_str += f"{i}:({val})," + last_val = val + # Remove the trailing comma from the raw frame string + raw_frame_str = raw_frame_str.rstrip(",") + # Add the raw frames for the current item to the export string + export_string += f"\nRaw frames for {key}:\n{raw_frame_str}\n" + + # Add a newline to the export string export_string += "\n" - - #Done saving all cameras, restore original animation frame + + # Restore the original frame scene.frame_set(currentFrame) + + # Return the export string return export_string -def write_camera_data(context, filepath, start, end, cams, scale, output_camcode, output_json): +# Write camera data to a file. +def write_camera_data(context, filepath, start, end, cams, scale, output_camcode, output_json, output_raw_frames): + # Log the start of the function print("running write_camera_data...") - outputString = cameras_to_string(context, start, end, cams, scale, output_camcode, output_json) + + # Generate a string representation of the camera data. The function cameras_to_string is not defined in this snippet. + outputString = cameras_to_string(context, start, end, cams, scale, output_camcode, output_json, output_raw_frames) + + # Open the output file with open(filepath, 'w', encoding='utf-8') as f: + # Write the frame range to the file f.write(f"Export frames {start} - {end}\n") + + # Write the names of the cameras to the file f.write(f"Export cameras {[c.name for c in cams]}\n") + + # Write the generated camera data string to the file f.write(outputString) + + # Return a status indicating that the function has finished return {'FINISHED'} - -# ExportHelper is a helper class, defines filename and -# invoke() function which calls the file selector. +# Import the ExportHelper class from the bpy_extras.io_utils module. This is a helper class for creating export operators in Blender. from bpy_extras.io_utils import ExportHelper + +# Import various property types from the bpy.props module. These are used to create properties for Blender operators. from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty + +# Import the Operator class from the bpy.types module. This is the base class for all operator types in Blender. from bpy.types import Operator +# Define the ExportDiffusionString class, which is a type of Blender operator for exporting scene data. class ExportDiffusionString(Operator, ExportHelper): - """Export animation keyframes in the format of Deforum Diffusion camera animation keyframes""" - bl_idname = "export_scene.diffusion" # important since its how bpy.ops.import_test.some_data is constructed + + # The bl_idname attribute represents the unique identifier for this operator. + bl_idname = "export_scene.diffusion" + + # The bl_label attribute represents the name that will be displayed in the user interface for this operator. bl_label = "Export Diffusion" - - # ExportHelper mixin class uses this + + # The filename_ext attribute represents the default file extension for exported files. filename_ext = ".txt" - + + # A string property that represents the type of files that can be selected in the file browser. + # Default is set to "*.txt" and is hidden from the user interface. filter_glob: StringProperty( default="*.txt", options={'HIDDEN'}, - maxlen=255, # Max internal buffer length, longer would be clamped. + maxlen=255, ) - - -# animation_mode: EnumProperty( -# name="Animation Mode", -# description="2d or 3d. (only 3d supported currently)", -# items=( -# ('3D', "3d", "3d Camera Movement"), -# ), -# default='3D', -# ) + # A boolean property to decide whether to output data in JSON format. Default is set to False. output_json: BoolProperty(name="Output JSON", default=False) - output_cam_code: BoolProperty(name="Output cam_code", default=True) + # A boolean property to decide whether to output camera code. Default is set to False. + output_cam_code: BoolProperty(name="Output cam_code", default=False) + + # A boolean property to decide whether to output raw frames. Default is set to True. + output_raw_frames: BoolProperty(name="Output Raw Frames", default=True) + + # An integer property representing the start frame for export. Default is set to -1. frame_start: IntProperty(name="Start", default=-1) - frame_end: IntProperty(name="End", default=-1)#bpy.context.scene.frame_end + # An integer property representing the end frame for export. Default is set to -1. + frame_end: IntProperty(name="End", default=-1) + + # An enumerated property representing which cameras to export. Options include the active camera, + # selected cameras only, or all cameras in the scene. Default is set to the active camera. which_cams: EnumProperty( name="Which Cams", description="Which cameras to exprot", @@ -179,67 +260,163 @@ class ExportDiffusionString(Operator, ExportHelper): default='ACTIVE', ) + # A float property representing the conversion factor between Blender units and Diffusion units. + # Default is set to 50. translation_scale: FloatProperty(default=50, name="Translation Scale", description = "Conversion factor between blender units and Diffusion units") - def draw(self, context): - layout = self.layout - row = layout.row() - row.label(text="Which Cameras") - row = layout.row() - row.props_enum(self, "which_cams") - - row = layout.row() - row.label(text="Export Settings") - - row = layout.row() - row.label(text="Frames") - if self.frame_start == -1: - self.frame_start = bpy.context.scene.frame_start - if self.frame_end == -1: - self.frame_end = bpy.context.scene.frame_end - row.prop(self, "frame_start") - row.prop(self, "frame_end") - -# row = layout.row() -# row.prop(self, "animation_mode") - row = layout.row() - row.prop(self, "translation_scale") +# This function is responsible for creating the user interface +def draw(self, context): + layout = self.layout # get the layout for the current context + + # create a new row and add a label + row = layout.row() + row.label(text="Which Cameras") + + # create a new row and add a property for choosing cameras + row = layout.row() + row.props_enum(self, "which_cams") + + # create a new row and add a label + row = layout.row() + row.label(text="Export Settings") + + # create a new row and add a label + row = layout.row() + row.label(text="Frames") + + # set default values for frame_start and frame_end if they are not set + if self.frame_start == -1: + self.frame_start = bpy.context.scene.frame_start + if self.frame_end == -1: + self.frame_end = bpy.context.scene.frame_end + + # create a new row and add properties for frame_start and frame_end + row.prop(self, "frame_start") + row.prop(self, "frame_end") + + # create a new row and add a property for translation_scale + row = layout.row() + row.prop(self, "translation_scale") + + # create a new row and add a property for output_cam_code + row = layout.row() + row.prop(self, "output_cam_code") + + # create a new row and add a property for output_json + row = layout.row() + row.prop(self, "output_json") + + # create a new row and add a property for output_raw_frames + row = layout.row() + row.prop(self, "output_raw_frames") - row = layout.row() - row.prop(self, "output_cam_code") - row = layout.row() - row.prop(self, "output_json") + +# This function is responsible for executing the main operation of the addon or script +def execute(self, context): + export_cams = [] # initialize an empty list for cameras to be exported + + # decide which cameras to export based on the selected option + if self.which_cams == "ACTIVE": + export_cams = [context.scene.camera] # get the active camera + elif self.which_cams == "SELECTED": + # get all selected objects that are cameras + export_cams = [cam for cam in context.selected_objects if cam.type == 'CAMERA'] + elif self.which_cams == "ALL": + # get all objects in the scene that are cameras + export_cams = [cam for cam in context.scene.objects if cam.type == 'CAMERA'] - def execute(self, context): - export_cams = [] - if self.which_cams == "ACTIVE": - export_cams = [context.scene.camera] - elif self.which_cams == "SELECTED": - export_cams = [cam for cam in context.selected_objects if cam.type == 'CAMERA'] - elif self.which_cams == "ALL": - export_cams = [cam for cam in context.scene.objects if cam.type == 'CAMERA'] - return write_camera_data(context, self.filepath, self.frame_start, self.frame_end, export_cams, - self.translation_scale, self.output_cam_code, self.output_json) - - -# Only needed if you want to add into a dynamic menu -def menu_func_export(self, context): - self.layout.operator(ExportDiffusionString.bl_idname, text="Diffusion Notebook String") + # call the write_camera_data function with the specified parameters + return write_camera_data(context, self.filepath, self.frame_start, self.frame_end, export_cams, self.translation_scale, self.output_cam_code, self.output_json, self.output_raw_frames) + +# This class is responsible for importing a diffusion string from a file. +# It extends from both a Blender Operator and ImportHelper, which provides a file dialog for importing files. +class ImportDiffusionString(Operator, ImportHelper): + bl_idname = "import_scene.diffusion" # the unique identifier for this operator + bl_label = "Import Diffusion" # the label for this operator + filename_ext = ".txt" # the extension of files that can be imported by this operator + + # A Blender StringProperty to specify the types of files that can be imported + filter_glob: StringProperty( + default="*.txt", # default value is any txt file + options={'HIDDEN'}, # this property is hidden + maxlen=255, # maximum length of the string is 255 + ) + +# tuple of classes to be registered +classes = ( + ExportDiffusionString, + ImportDiffusionString, +) + +# register or unregister classes if this script is run as the main script +if __name__ == "__main__": + from bpy.utils import register_class, unregister_class + for cls in classes: + register_class(cls) # register each class + +# This function is responsible for executing the import operation +def execute(self, context): + print("Executing import operator...") # log to console + with open(self.filepath, 'r', encoding='utf-8') as f: # open the selected file + content = f.read() # read the file content + keyframe_data = json.loads(content) # parse the file content as JSON + + camera_obj = None + for obj in context.scene.objects: # iterate over all objects in the scene + if obj.type == 'CAMERA': # if the object is a camera + camera_obj = obj # use it as the camera object + break + + if camera_obj is None: # if no camera object was found + bpy.ops.object.camera_add() # add a new camera object + camera_obj = bpy.context.active_object # use the newly added camera as the camera object + for frame, data in keyframe_data.items(): # iterate over all items in the keyframe data + camera_obj.location = data["location"] # set the location of the camera object + camera_obj.rotation_euler = data["rotation"] # set the rotation of the camera object + camera_obj.keyframe_insert(data_path="location", frame=frame) # insert a location keyframe + camera_obj.keyframe_insert(data_path="rotation_euler", frame=frame) # insert a rotation keyframe -# Register and add to the "file selector" menu (required to use F3 search "Text Export Operator" for quick access). + # It's unclear what this return statement does without context about write_camera_data function. + # Assuming it writes camera data to somewhere. + return write_camera_data(context, self.filepath, self.frame_start, self.frame_end, export_cams, + self.translation_scale, self.output_cam_code, self.output_json, self.output_raw_frames) + + +# Import option for Diffusion (.txt) files to the menu +def menu_func_import(self, context): + # Add the operator to the layout + self.layout.operator(ImportDiffusionString.bl_idname, text="Diffusion (.txt)") + +# Export option for Diffusion (.txt) files to the menu +def menu_func_export(self, context): + # Add the operator to the layout + self.layout.operator(ExportDiffusionString.bl_idname, text="Diffusion (.txt)") + +# Registers the operators and adds them to the import and export menus def register(): + # Register the ImportDiffusionString operator + bpy.utils.register_class(ImportDiffusionString) + # Add the import function to the File > Import menu + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + + # Register the ExportDiffusionString operator bpy.utils.register_class(ExportDiffusionString) + # Add the export function to the File > Export menu bpy.types.TOPBAR_MT_file_export.append(menu_func_export) - +# Unregisters the operators and removes them from the import and export menus def unregister(): + # Unregister the ImportDiffusionString operator + bpy.utils.unregister_class(ImportDiffusionString) + # Remove the import function from the File > Import menu + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + + # Unregister the ExportDiffusionString operator bpy.utils.unregister_class(ExportDiffusionString) + # Remove the export function from the File > Export menu bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) - + if __name__ == "__main__": - register() - - # test call - bpy.ops.export_scene.diffusion('INVOKE_DEFAULT') + register() \ No newline at end of file From dbed6b2f85af5037afa008d6ee845777a97a35d4 Mon Sep 17 00:00:00 2001 From: Kewk Date: Sat, 13 May 2023 15:39:16 -0700 Subject: [PATCH 2/4] checked in my test file, fix for that here. --- export_diffusion.py | 297 ++++++++++++++------------------------------ 1 file changed, 92 insertions(+), 205 deletions(-) diff --git a/export_diffusion.py b/export_diffusion.py index 0e03df5..0072eb2 100644 --- a/export_diffusion.py +++ b/export_diffusion.py @@ -1,6 +1,6 @@ bl_info = { "name": "Export Camera Animation to Deforum collab/webui", - "author": "Michael Walker (@mwalk10) / Kewk (@KewkD)", + "author": "Michael Walker (@mwalk10)", "version": (1, 2, 0), "blender": (3, 5, 1), "location": "File > Export > Diffusion Notebook String", @@ -18,117 +18,67 @@ import json import re -# Define a function to round small numbers to zero, with a customisable threshold def roundZero(num, magnitude_thresh = 0.00001): - # If the absolute value of the number is greater than the threshold if abs(num) > magnitude_thresh: - # Return the original number return num else: - # Otherwise, return 0 return 0 - -# Define a function to convert an array into a string of keyframes + def arr_to_keyframes(arr): - # Initialize an empty string to store the keyframes keyframes = "" - # Enumerate over the array (i will be the index, val will be the value at that index) for i, val in enumerate(arr): - # Round the current value to zero if it's smaller than the threshold val = roundZero(val) - # Check if the current value is close to the previous one last_is_same = i > 0 and isclose(val, roundZero(arr[i-1])) - # Check if the current value is close to the next one next_is_same = (i+1) < len(arr) and isclose(val, roundZero(arr[i+1])) - # Determine if the current value should be omitted (it's the same as both the previous and next values) omit = last_is_same and next_is_same - # If the current value shouldn't be omitted if not omit: - # Add it to the keyframes string, formatted as "index:(value)," keyframes += f"{i}:({val})," - # Return the keyframes string return keyframes - -# This function transforms camera data to a string representation. def cameras_to_string(context, startFrame, endFrame, cameras, translation_scale = 50, output_camcode = True, output_json = False, output_raw_frames = False): - # Get the current scene scene = context.scene - # Save the current frame - currentFrame = scene.frame_current - - # If no cameras are selected, print a message and return + currentFrame = scene.frame_current if len(cameras) == 0: print("Nothing selected!") return "No Cameras selected for export" - - # Initialize an empty string for the export export_string = "" - - # Loop over all selected cameras for sel in cameras: - # Set the scene frame to the start frame - scene.frame_set(startFrame) - - # Initialize lists to store translation and rotation values + scene.frame_set(startFrame) translation_x = [] translation_y = [] translation_z = [] rotation_3d_x = [] rotation_3d_y = [] - rotation_3d_z = [] - - # Save the current camera's world matrix and rotation + rotation_3d_z = [] oldMat = sel.matrix_world.copy() oldRot = oldMat.to_quaternion() - - # Loop over all frames from start to end for frame in range(startFrame+1, endFrame): - # Set the scene frame to the current frame - scene.frame_set(frame) - - # Save the current camera's world matrix and rotation + scene.frame_set(frame) newMat = sel.matrix_world.copy() - newRot = newMat.to_quaternion() - - # Get the inverse of the new world matrix and its rotation + newRot = newMat.to_quaternion() worldToLocal = newMat.inverted() - wlRot = worldToLocal.to_quaternion() - - # Calculate the difference in position between the old and new frames + wlRot = worldToLocal.to_quaternion() posDiff = newMat.to_translation() - oldMat.to_translation() - posDiffLocal = wlRot @ posDiff - - # Store the scaled translation values + posDiffLocal = wlRot @ posDiff translation_x.append(translation_scale*posDiffLocal.x) translation_y.append(translation_scale*posDiffLocal.y) - translation_z.append(-translation_scale*posDiffLocal.z) - - # Calculate the difference in rotation between the old and new frames - rotDiff = oldRot.rotation_difference(newRot).to_euler("XYZ") - - # Store the rotation values in degrees + translation_z.append(-translation_scale*posDiffLocal.z) + rotDiff = oldRot.rotation_difference(newRot).to_euler("XYZ") rotation_3d_x.append(degrees(rotDiff.x)) rotation_3d_y.append(degrees(-rotDiff.y)) - rotation_3d_z.append(degrees(-rotDiff.z)) - - # Update the old matrix and rotation for the next frame + rotation_3d_z.append(degrees(-rotDiff.z)) oldMat = newMat oldRot = newRot - - # Check if raw frames should be output if not output_raw_frames: - # Add camera export data to the export string export_string += f"\nCamera Export: {sel.name}\n" - for var, label in zip([translation_x, translation_y, translation_z, rotation_3d_x, rotation_3d_y, rotation_3d_z], - ['translation_x', 'translation_y', 'translation_z', 'rotation_3d_x', 'rotation_3d_y', 'rotation_3d_z']): - export_string += f'{label} = "{arr_to_keyframes(var)}" #@param {{type:"string"}}\n' - - # Check if camera code should be output + export_string += f'translation_x = "{arr_to_keyframes(translation_x)}" #@param {{type:"string"}}\n' + export_string += f'translation_y = "{arr_to_keyframes(translation_y)}" #@param {{type:"string"}}\n' + export_string += f'translation_z = "{arr_to_keyframes(translation_z)}" #@param {{type:"string"}}\n' + export_string += f'rotation_3d_x = "{arr_to_keyframes(rotation_3d_x)}" #@param {{type:"string"}}\n' + export_string += f'rotation_3d_y = "{arr_to_keyframes(rotation_3d_y)}" #@param {{type:"string"}}\n' + export_string += f'rotation_3d_z = "{arr_to_keyframes(rotation_3d_z)}" #@param {{type:"string"}}\n' if output_camcode: - # Add camera code to the export string - export_string += f'cam_code:\n(translation_x,translation_y,translation_z,rotation_3d_x,rotation_3d_y,rotation_3d_z) = ("{arr_to_keyframes(translation_x)}", "{arr_to_keyframes(translation_y)}", "{arr_to_keyframes(translation_z)}", "{arr_to_keyframes(rotation_3d_x)}", "{arr_to_keyframes(rotation_3d_y)}", "{arr_to_keyframes(rotation_3d_z)}")\n' - + export_string += f'cam_code:\n(translation_x,translation_y,translation_z,rotation_3d_x,rotation_3d_y,rotation_3d_z) = ("{arr_to_keyframes(translation_x)}", "{arr_to_keyframes(translation_y)}", "{arr_to_keyframes(translation_z)}", "{arr_to_keyframes(rotation_3d_x)}", "{arr_to_keyframes(rotation_3d_y)}", "{arr_to_keyframes(rotation_3d_z)}")\n' # Check if JSON data should be output if output_json: # Create a dictionary with all the camera data @@ -154,7 +104,6 @@ def cameras_to_string(context, startFrame, endFrame, cameras, translation_scale "rotation_3d_y": rotation_3d_y, "rotation_3d_z": rotation_3d_z } - # Loop over all items in the dictionary for key, arr in raw_frames.items(): # Initialize the string for raw frames @@ -180,37 +129,31 @@ def cameras_to_string(context, startFrame, endFrame, cameras, translation_scale # Return the export string return export_string -# Write camera data to a file. + def write_camera_data(context, filepath, start, end, cams, scale, output_camcode, output_json, output_raw_frames): - # Log the start of the function print("running write_camera_data...") - # Generate a string representation of the camera data. The function cameras_to_string is not defined in this snippet. outputString = cameras_to_string(context, start, end, cams, scale, output_camcode, output_json, output_raw_frames) - - # Open the output file with open(filepath, 'w', encoding='utf-8') as f: - # Write the frame range to the file f.write(f"Export frames {start} - {end}\n") - - # Write the names of the cameras to the file f.write(f"Export cameras {[c.name for c in cams]}\n") - - # Write the generated camera data string to the file f.write(outputString) - - # Return a status indicating that the function has finished return {'FINISHED'} - -# Import the ExportHelper class from the bpy_extras.io_utils module. This is a helper class for creating export operators in Blender. from bpy_extras.io_utils import ExportHelper - -# Import various property types from the bpy.props module. These are used to create properties for Blender operators. from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty - -# Import the Operator class from the bpy.types module. This is the base class for all operator types in Blender. from bpy.types import Operator +def write_camera_data(context, filepath, start, end, cams, scale, output_camcode, output_json, output_raw_frames): + print("running write_camera_data...") + outputString = cameras_to_string(context, start, end, cams, scale, output_camcode, output_json, output_raw_frames) + with open(filepath, 'w', encoding='utf-8') as f: + f.write(f"Export frames {start} - {end}\n") + f.write(f"Export cameras {[c.name for c in cams]}\n") + f.write(outputString) + return {'FINISHED'} +from bpy_extras.io_utils import ExportHelper +from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty +from bpy.types import Operator # Define the ExportDiffusionString class, which is a type of Blender operator for exporting scene data. class ExportDiffusionString(Operator, ExportHelper): @@ -231,7 +174,6 @@ class ExportDiffusionString(Operator, ExportHelper): options={'HIDDEN'}, maxlen=255, ) - # A boolean property to decide whether to output data in JSON format. Default is set to False. output_json: BoolProperty(name="Output JSON", default=False) @@ -247,8 +189,6 @@ class ExportDiffusionString(Operator, ExportHelper): # An integer property representing the end frame for export. Default is set to -1. frame_end: IntProperty(name="End", default=-1) - # An enumerated property representing which cameras to export. Options include the active camera, - # selected cameras only, or all cameras in the scene. Default is set to the active camera. which_cams: EnumProperty( name="Which Cams", description="Which cameras to exprot", @@ -258,91 +198,56 @@ class ExportDiffusionString(Operator, ExportHelper): ('ALL', "All", "All cameras in scene"), ), default='ACTIVE', - ) - - # A float property representing the conversion factor between Blender units and Diffusion units. - # Default is set to 50. + ) translation_scale: FloatProperty(default=50, name="Translation Scale", description = "Conversion factor between blender units and Diffusion units") +# Create the user interface + def draw(self, context): + layout = self.layout + row = layout.row() + row.label(text="Which Cameras") + row = layout.row() + row.props_enum(self, "which_cams") + row = layout.row() + row.label(text="Export Settings") + row = layout.row() + row.label(text="Frames") + if self.frame_start == -1: + self.frame_start = bpy.context.scene.frame_start + if self.frame_end == -1: + self.frame_end = bpy.context.scene.frame_end + row.prop(self, "frame_start") + row.prop(self, "frame_end") + row = layout.row() + row.prop(self, "translation_scale") + row = layout.row() + row.prop(self, "output_cam_code") + row = layout.row() + row.prop(self, "output_json") + row = layout.row() + row.prop(self, "output_raw_frames") + +# Execute the main operation of the addon or script + def execute(self, context): + export_cams = [] + if self.which_cams == "ACTIVE": + export_cams = [context.scene.camera] + elif self.which_cams == "SELECTED": + export_cams = [cam for cam in context.selected_objects if cam.type == 'CAMERA'] + elif self.which_cams == "ALL": + export_cams = [cam for cam in context.scene.objects if cam.type == 'CAMERA'] + return write_camera_data(context, self.filepath, self.frame_start, self.frame_end, export_cams, self.translation_scale, self.output_cam_code, self.output_json, self.output_raw_frames) -# This function is responsible for creating the user interface -def draw(self, context): - layout = self.layout # get the layout for the current context - - # create a new row and add a label - row = layout.row() - row.label(text="Which Cameras") - - # create a new row and add a property for choosing cameras - row = layout.row() - row.props_enum(self, "which_cams") - - # create a new row and add a label - row = layout.row() - row.label(text="Export Settings") - - # create a new row and add a label - row = layout.row() - row.label(text="Frames") - - # set default values for frame_start and frame_end if they are not set - if self.frame_start == -1: - self.frame_start = bpy.context.scene.frame_start - if self.frame_end == -1: - self.frame_end = bpy.context.scene.frame_end - - # create a new row and add properties for frame_start and frame_end - row.prop(self, "frame_start") - row.prop(self, "frame_end") - - # create a new row and add a property for translation_scale - row = layout.row() - row.prop(self, "translation_scale") - - # create a new row and add a property for output_cam_code - row = layout.row() - row.prop(self, "output_cam_code") - - # create a new row and add a property for output_json - row = layout.row() - row.prop(self, "output_json") - - # create a new row and add a property for output_raw_frames - row = layout.row() - row.prop(self, "output_raw_frames") - - -# This function is responsible for executing the main operation of the addon or script -def execute(self, context): - export_cams = [] # initialize an empty list for cameras to be exported - - # decide which cameras to export based on the selected option - if self.which_cams == "ACTIVE": - export_cams = [context.scene.camera] # get the active camera - elif self.which_cams == "SELECTED": - # get all selected objects that are cameras - export_cams = [cam for cam in context.selected_objects if cam.type == 'CAMERA'] - elif self.which_cams == "ALL": - # get all objects in the scene that are cameras - export_cams = [cam for cam in context.scene.objects if cam.type == 'CAMERA'] - - # call the write_camera_data function with the specified parameters - return write_camera_data(context, self.filepath, self.frame_start, self.frame_end, export_cams, self.translation_scale, self.output_cam_code, self.output_json, self.output_raw_frames) -# This class is responsible for importing a diffusion string from a file. -# It extends from both a Blender Operator and ImportHelper, which provides a file dialog for importing files. +# Import a diffusion string from a file. class ImportDiffusionString(Operator, ImportHelper): - bl_idname = "import_scene.diffusion" # the unique identifier for this operator - bl_label = "Import Diffusion" # the label for this operator - filename_ext = ".txt" # the extension of files that can be imported by this operator - - # A Blender StringProperty to specify the types of files that can be imported + bl_idname = "import_scene.diffusion" + bl_label = "Import Diffusion" + filename_ext = ".txt" filter_glob: StringProperty( - default="*.txt", # default value is any txt file - options={'HIDDEN'}, # this property is hidden - maxlen=255, # maximum length of the string is 255 + default="*.txt", + options={'HIDDEN'}, + maxlen=255, ) - -# tuple of classes to be registered classes = ( ExportDiffusionString, ImportDiffusionString, @@ -352,71 +257,53 @@ class ImportDiffusionString(Operator, ImportHelper): if __name__ == "__main__": from bpy.utils import register_class, unregister_class for cls in classes: - register_class(cls) # register each class + register_class(cls) -# This function is responsible for executing the import operation +# Execute the import operation def execute(self, context): - print("Executing import operator...") # log to console - with open(self.filepath, 'r', encoding='utf-8') as f: # open the selected file - content = f.read() # read the file content - keyframe_data = json.loads(content) # parse the file content as JSON - + print("Executing import operator...") + with open(self.filepath, 'r', encoding='utf-8') as f: + content = f.read() + keyframe_data = json.loads(content) camera_obj = None - for obj in context.scene.objects: # iterate over all objects in the scene - if obj.type == 'CAMERA': # if the object is a camera - camera_obj = obj # use it as the camera object + for obj in context.scene.objects: + if obj.type == 'CAMERA': + camera_obj = obj break - - if camera_obj is None: # if no camera object was found - bpy.ops.object.camera_add() # add a new camera object - camera_obj = bpy.context.active_object # use the newly added camera as the camera object - - for frame, data in keyframe_data.items(): # iterate over all items in the keyframe data - camera_obj.location = data["location"] # set the location of the camera object - camera_obj.rotation_euler = data["rotation"] # set the rotation of the camera object - camera_obj.keyframe_insert(data_path="location", frame=frame) # insert a location keyframe - camera_obj.keyframe_insert(data_path="rotation_euler", frame=frame) # insert a rotation keyframe - - # It's unclear what this return statement does without context about write_camera_data function. - # Assuming it writes camera data to somewhere. + if camera_obj is None: + bpy.ops.object.camera_add() + camera_obj = bpy.context.active_object + for frame, data in keyframe_data.items(): + camera_obj.location = data["location"] + camera_obj.rotation_euler = data["rotation"] + camera_obj.keyframe_insert(data_path="location", frame=frame) + camera_obj.keyframe_insert(data_path="rotation_euler", frame=frame) return write_camera_data(context, self.filepath, self.frame_start, self.frame_end, export_cams, self.translation_scale, self.output_cam_code, self.output_json, self.output_raw_frames) - # Import option for Diffusion (.txt) files to the menu def menu_func_import(self, context): - # Add the operator to the layout self.layout.operator(ImportDiffusionString.bl_idname, text="Diffusion (.txt)") # Export option for Diffusion (.txt) files to the menu def menu_func_export(self, context): - # Add the operator to the layout self.layout.operator(ExportDiffusionString.bl_idname, text="Diffusion (.txt)") # Registers the operators and adds them to the import and export menus def register(): - # Register the ImportDiffusionString operator bpy.utils.register_class(ImportDiffusionString) - # Add the import function to the File > Import menu bpy.types.TOPBAR_MT_file_import.append(menu_func_import) - # Register the ExportDiffusionString operator bpy.utils.register_class(ExportDiffusionString) - # Add the export function to the File > Export menu bpy.types.TOPBAR_MT_file_export.append(menu_func_export) # Unregisters the operators and removes them from the import and export menus def unregister(): - # Unregister the ImportDiffusionString operator bpy.utils.unregister_class(ImportDiffusionString) - # Remove the import function from the File > Import menu bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) - # Unregister the ExportDiffusionString operator bpy.utils.unregister_class(ExportDiffusionString) - # Remove the export function from the File > Export menu bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) - if __name__ == "__main__": register() \ No newline at end of file From 3ed04940471bce01fded1ee9b96f9eb3f2606765 Mon Sep 17 00:00:00 2001 From: Kewk <57611539+KewkLW@users.noreply.github.com> Date: Fri, 12 Apr 2024 13:27:55 -0700 Subject: [PATCH 3/4] Update export_diffusion.py Fixed loading from zip. Can now load from py file --- export_diffusion.py | 268 ++++++-------------------------------------- 1 file changed, 37 insertions(+), 231 deletions(-) diff --git a/export_diffusion.py b/export_diffusion.py index 0072eb2..5dbe1fd 100644 --- a/export_diffusion.py +++ b/export_diffusion.py @@ -1,101 +1,92 @@ bl_info = { "name": "Export Camera Animation to Deforum collab/webui", - "author": "Michael Walker (@mwalk10)", - "version": (1, 2, 0), - "blender": (3, 5, 1), - "location": "File > Export > Diffusion Notebook String", - "description": "Export camera animations formatted for use in Deforum: collab and Webui's", + "author": "Kewk", + "version": (1, 4, 0), + "blender": (4, 1, 0), + "location": "File > Export > Diffusion String", + "description": "Export camera animations formatted for use in Deforum", "warning": "", "wiki_url": "", "category": "Import-Export", } - import bpy -from bpy_extras.io_utils import ImportHelper -from math import degrees -from math import isclose -import json -import re +from bpy_extras.io_utils import ImportHelper, ExportHelper +from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty +from bpy.types import Operator -def roundZero(num, magnitude_thresh = 0.00001): +def roundZero(num, magnitude_thresh=0.00001): if abs(num) > magnitude_thresh: return num else: return 0 - + def arr_to_keyframes(arr): keyframes = "" for i, val in enumerate(arr): val = roundZero(val) last_is_same = i > 0 and isclose(val, roundZero(arr[i-1])) - next_is_same = (i+1) < len(arr) and isclose(val, roundZero(arr[i+1])) - omit = last_is_same and next_is_same + next_is_same = (i+1) < len(arr) and isclose(val, roundZero(arr[i+1])) + omit = last_is_same and next_is_same if not omit: keyframes += f"{i}:({val})," return keyframes - -def cameras_to_string(context, startFrame, endFrame, cameras, translation_scale = 50, output_camcode = True, output_json = False, output_raw_frames = False): + +def cameras_to_string(context, startFrame, endFrame, cameras, translation_scale=50, output_camcode=True, output_json=False, output_raw_frames=False): scene = context.scene - currentFrame = scene.frame_current + currentFrame = scene.frame_current if len(cameras) == 0: print("Nothing selected!") return "No Cameras selected for export" export_string = "" for sel in cameras: - scene.frame_set(startFrame) + scene.frame_set(startFrame) translation_x = [] translation_y = [] translation_z = [] rotation_3d_x = [] rotation_3d_y = [] - rotation_3d_z = [] + rotation_3d_z = [] oldMat = sel.matrix_world.copy() oldRot = oldMat.to_quaternion() for frame in range(startFrame+1, endFrame): - scene.frame_set(frame) - newMat = sel.matrix_world.copy() - newRot = newMat.to_quaternion() + scene.frame_set(frame) + newMat = sel.matrix_world.copy() + newRot = newMat.to_quaternion() worldToLocal = newMat.inverted() - wlRot = worldToLocal.to_quaternion() + wlRot = worldToLocal.to_quaternion() posDiff = newMat.to_translation() - oldMat.to_translation() - posDiffLocal = wlRot @ posDiff + posDiffLocal = wlRot @ posDiff translation_x.append(translation_scale*posDiffLocal.x) translation_y.append(translation_scale*posDiffLocal.y) - translation_z.append(-translation_scale*posDiffLocal.z) - rotDiff = oldRot.rotation_difference(newRot).to_euler("XYZ") + translation_z.append(-translation_scale*posDiffLocal.z) + rotDiff = oldRot.rotation_difference(newRot).to_euler("XYZ") rotation_3d_x.append(degrees(rotDiff.x)) rotation_3d_y.append(degrees(-rotDiff.y)) - rotation_3d_z.append(degrees(-rotDiff.z)) + rotation_3d_z.append(degrees(-rotDiff.z)) oldMat = newMat oldRot = newRot if not output_raw_frames: - export_string += f"\nCamera Export: {sel.name}\n" + export_string += f"\nCamera Export: {sel.name}\n" export_string += f'translation_x = "{arr_to_keyframes(translation_x)}" #@param {{type:"string"}}\n' export_string += f'translation_y = "{arr_to_keyframes(translation_y)}" #@param {{type:"string"}}\n' export_string += f'translation_z = "{arr_to_keyframes(translation_z)}" #@param {{type:"string"}}\n' export_string += f'rotation_3d_x = "{arr_to_keyframes(rotation_3d_x)}" #@param {{type:"string"}}\n' export_string += f'rotation_3d_y = "{arr_to_keyframes(rotation_3d_y)}" #@param {{type:"string"}}\n' - export_string += f'rotation_3d_z = "{arr_to_keyframes(rotation_3d_z)}" #@param {{type:"string"}}\n' + export_string += f'rotation_3d_z = "{arr_to_keyframes(rotation_3d_z)}" #@param {{type:"string"}}\n' if output_camcode: - export_string += f'cam_code:\n(translation_x,translation_y,translation_z,rotation_3d_x,rotation_3d_y,rotation_3d_z) = ("{arr_to_keyframes(translation_x)}", "{arr_to_keyframes(translation_y)}", "{arr_to_keyframes(translation_z)}", "{arr_to_keyframes(rotation_3d_x)}", "{arr_to_keyframes(rotation_3d_y)}", "{arr_to_keyframes(rotation_3d_z)}")\n' - # Check if JSON data should be output + export_string += f'cam_code:\n(translation_x,translation_y,translation_z,rotation_3d_x,rotation_3d_y,rotation_3d_z) = ("{arr_to_keyframes(translation_x)}", "{arr_to_keyframes(translation_y)}", "{arr_to_keyframes(translation_z)}", "{arr_to_keyframes(rotation_3d_x)}", "{arr_to_keyframes(rotation_3d_y)}", "{arr_to_keyframes(rotation_3d_z)}")\n' if output_json: - # Create a dictionary with all the camera data jsondict = { - "translation_x" : translation_x, - "translation_y" : translation_y, - "translation_z" : translation_z, - "rotation_3d_x" : rotation_3d_x, - "rotation_3d_y" : rotation_3d_y, - "rotation_3d_z" : rotation_3d_z} - - # Add the JSON representation of the dictionary to the export string + "translation_x": translation_x, + "translation_y": translation_y, + "translation_z": translation_z, + "rotation_3d_x": rotation_3d_x, + "rotation_3d_y": rotation_3d_y, + "rotation_3d_z": rotation_3d_z + } export_string += f"JSON:\n {json.dumps(jsondict)}\n" - - # Check if raw frame data should be output if output_raw_frames: - # Create a dictionary with all the raw frame data raw_frames = { "translation_x": translation_x, "translation_y": translation_y, @@ -104,206 +95,21 @@ def cameras_to_string(context, startFrame, endFrame, cameras, translation_scale "rotation_3d_y": rotation_3d_y, "rotation_3d_z": rotation_3d_z } - # Loop over all items in the dictionary for key, arr in raw_frames.items(): - # Initialize the string for raw frames - raw_frame_str = "" + raw_frame_str = "" last_val = None for i, val in enumerate(arr): - # Check if the value has changed from the last frame if last_val is None or not isclose(val, last_val): - # Add the frame index and value to the raw frame string raw_frame_str += f"{i}:({val})," last_val = val - # Remove the trailing comma from the raw frame string raw_frame_str = raw_frame_str.rstrip(",") - # Add the raw frames for the current item to the export string export_string += f"\nRaw frames for {key}:\n{raw_frame_str}\n" - - # Add a newline to the export string export_string += "\n" - - # Restore the original frame scene.frame_set(currentFrame) - - # Return the export string return export_string - def write_camera_data(context, filepath, start, end, cams, scale, output_camcode, output_json, output_raw_frames): print("running write_camera_data...") - # Generate a string representation of the camera data. The function cameras_to_string is not defined in this snippet. outputString = cameras_to_string(context, start, end, cams, scale, output_camcode, output_json, output_raw_frames) with open(filepath, 'w', encoding='utf-8') as f: - f.write(f"Export frames {start} - {end}\n") - f.write(f"Export cameras {[c.name for c in cams]}\n") - f.write(outputString) - return {'FINISHED'} -from bpy_extras.io_utils import ExportHelper -from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty -from bpy.types import Operator - -def write_camera_data(context, filepath, start, end, cams, scale, output_camcode, output_json, output_raw_frames): - print("running write_camera_data...") - outputString = cameras_to_string(context, start, end, cams, scale, output_camcode, output_json, output_raw_frames) - with open(filepath, 'w', encoding='utf-8') as f: - f.write(f"Export frames {start} - {end}\n") - f.write(f"Export cameras {[c.name for c in cams]}\n") - f.write(outputString) - return {'FINISHED'} -from bpy_extras.io_utils import ExportHelper -from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty -from bpy.types import Operator - -# Define the ExportDiffusionString class, which is a type of Blender operator for exporting scene data. -class ExportDiffusionString(Operator, ExportHelper): - - # The bl_idname attribute represents the unique identifier for this operator. - bl_idname = "export_scene.diffusion" - - # The bl_label attribute represents the name that will be displayed in the user interface for this operator. - bl_label = "Export Diffusion" - - # The filename_ext attribute represents the default file extension for exported files. - filename_ext = ".txt" - - # A string property that represents the type of files that can be selected in the file browser. - # Default is set to "*.txt" and is hidden from the user interface. - filter_glob: StringProperty( - default="*.txt", - options={'HIDDEN'}, - maxlen=255, - ) - # A boolean property to decide whether to output data in JSON format. Default is set to False. - output_json: BoolProperty(name="Output JSON", default=False) - - # A boolean property to decide whether to output camera code. Default is set to False. - output_cam_code: BoolProperty(name="Output cam_code", default=False) - - # A boolean property to decide whether to output raw frames. Default is set to True. - output_raw_frames: BoolProperty(name="Output Raw Frames", default=True) - - # An integer property representing the start frame for export. Default is set to -1. - frame_start: IntProperty(name="Start", default=-1) - - # An integer property representing the end frame for export. Default is set to -1. - frame_end: IntProperty(name="End", default=-1) - - which_cams: EnumProperty( - name="Which Cams", - description="Which cameras to exprot", - items=( - ('ACTIVE', "Active", "Scene's active camera"), - ('SELECTED', "Selected", "Selected cameras only"), - ('ALL', "All", "All cameras in scene"), - ), - default='ACTIVE', - ) - translation_scale: FloatProperty(default=50, name="Translation Scale", description = "Conversion factor between blender units and Diffusion units") -# Create the user interface - def draw(self, context): - layout = self.layout - row = layout.row() - row.label(text="Which Cameras") - row = layout.row() - row.props_enum(self, "which_cams") - row = layout.row() - row.label(text="Export Settings") - row = layout.row() - row.label(text="Frames") - if self.frame_start == -1: - self.frame_start = bpy.context.scene.frame_start - if self.frame_end == -1: - self.frame_end = bpy.context.scene.frame_end - row.prop(self, "frame_start") - row.prop(self, "frame_end") - row = layout.row() - row.prop(self, "translation_scale") - row = layout.row() - row.prop(self, "output_cam_code") - row = layout.row() - row.prop(self, "output_json") - row = layout.row() - row.prop(self, "output_raw_frames") - -# Execute the main operation of the addon or script - def execute(self, context): - export_cams = [] - if self.which_cams == "ACTIVE": - export_cams = [context.scene.camera] - elif self.which_cams == "SELECTED": - export_cams = [cam for cam in context.selected_objects if cam.type == 'CAMERA'] - elif self.which_cams == "ALL": - export_cams = [cam for cam in context.scene.objects if cam.type == 'CAMERA'] - return write_camera_data(context, self.filepath, self.frame_start, self.frame_end, export_cams, self.translation_scale, self.output_cam_code, self.output_json, self.output_raw_frames) - - -# Import a diffusion string from a file. -class ImportDiffusionString(Operator, ImportHelper): - bl_idname = "import_scene.diffusion" - bl_label = "Import Diffusion" - filename_ext = ".txt" - filter_glob: StringProperty( - default="*.txt", - options={'HIDDEN'}, - maxlen=255, - ) -classes = ( - ExportDiffusionString, - ImportDiffusionString, -) - -# register or unregister classes if this script is run as the main script -if __name__ == "__main__": - from bpy.utils import register_class, unregister_class - for cls in classes: - register_class(cls) - -# Execute the import operation -def execute(self, context): - print("Executing import operator...") - with open(self.filepath, 'r', encoding='utf-8') as f: - content = f.read() - keyframe_data = json.loads(content) - camera_obj = None - for obj in context.scene.objects: - if obj.type == 'CAMERA': - camera_obj = obj - break - if camera_obj is None: - bpy.ops.object.camera_add() - camera_obj = bpy.context.active_object - for frame, data in keyframe_data.items(): - camera_obj.location = data["location"] - camera_obj.rotation_euler = data["rotation"] - camera_obj.keyframe_insert(data_path="location", frame=frame) - camera_obj.keyframe_insert(data_path="rotation_euler", frame=frame) - return write_camera_data(context, self.filepath, self.frame_start, self.frame_end, export_cams, - self.translation_scale, self.output_cam_code, self.output_json, self.output_raw_frames) - -# Import option for Diffusion (.txt) files to the menu -def menu_func_import(self, context): - self.layout.operator(ImportDiffusionString.bl_idname, text="Diffusion (.txt)") - -# Export option for Diffusion (.txt) files to the menu -def menu_func_export(self, context): - self.layout.operator(ExportDiffusionString.bl_idname, text="Diffusion (.txt)") - -# Registers the operators and adds them to the import and export menus -def register(): - bpy.utils.register_class(ImportDiffusionString) - bpy.types.TOPBAR_MT_file_import.append(menu_func_import) - - bpy.utils.register_class(ExportDiffusionString) - bpy.types.TOPBAR_MT_file_export.append(menu_func_export) - -# Unregisters the operators and removes them from the import and export menus -def unregister(): - bpy.utils.unregister_class(ImportDiffusionString) - bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) - - bpy.utils.unregister_class(ExportDiffusionString) - bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) - -if __name__ == "__main__": - register() \ No newline at end of file + f.write(f"Export frames {start} From 066a3de147fbb018bea17199bd95d12884d98db8 Mon Sep 17 00:00:00 2001 From: Kewk <57611539+KewkLW@users.noreply.github.com> Date: Fri, 12 Apr 2024 14:01:53 -0700 Subject: [PATCH 4/4] Update export_diffusion.py --- export_diffusion.py | 86 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/export_diffusion.py b/export_diffusion.py index 5dbe1fd..1bdbf40 100644 --- a/export_diffusion.py +++ b/export_diffusion.py @@ -14,6 +14,9 @@ from bpy_extras.io_utils import ImportHelper, ExportHelper from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty from bpy.types import Operator +from math import degrees +from math import isclose +import json def roundZero(num, magnitude_thresh=0.00001): if abs(num) > magnitude_thresh: @@ -112,4 +115,85 @@ def write_camera_data(context, filepath, start, end, cams, scale, output_camcode print("running write_camera_data...") outputString = cameras_to_string(context, start, end, cams, scale, output_camcode, output_json, output_raw_frames) with open(filepath, 'w', encoding='utf-8') as f: - f.write(f"Export frames {start} + f.write(f"Export frames {start} - {end}\n") + f.write(outputString) + return {'FINISHED'} + +class ExportDiffusionString(Operator, ExportHelper): + bl_idname = "export_scene.diffusion_string" + bl_label = "Export Diffusion String" + + filename_ext = ".txt" + + filter_glob: StringProperty( + default="*.txt", + options={'HIDDEN'}, + maxlen=255, + ) + + start_frame: IntProperty( + name="Start Frame", + description="Starting frame for the export", + default=1, + min=1, + max=300000 + ) + + end_frame: IntProperty( + name="End Frame", + description="End frame for the export", + default=250, + min=1, + max=300000 + ) + + translation_scale: FloatProperty( + name="Position Scale", + description="Scales camera motion. Higher values make the camera move more", + default=1, + min=0, + max=1000 + ) + + output_camcode: BoolProperty( + name="Output Camcode", + description="Output a code block formatted for use as a camcode input", + default=True + ) + + output_json: BoolProperty( + name="Output JSON", + description="Output a JSON formatted dictionary", + default=False + ) + + output_raw_frames: BoolProperty( + name="Output Raw Frames", + description="Output raw translation/rotation values for each frame for each axis", + default=False + ) + + def execute(self, context): + start = self.start_frame + end = self.end_frame + cams = context.selected_objects + scale = self.translation_scale + camcode = self.output_camcode + json_output = self.output_json + raw_frames = self.output_raw_frames + write_camera_data(context, self.filepath, start, end, cams, scale, camcode, json_output, raw_frames) + return {'FINISHED'} + +def menu_func_export(self, context): + self.layout.operator(ExportDiffusionString.bl_idname, text="Diffusion String (.txt)") + +def register(): + bpy.utils.register_class(ExportDiffusionString) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) + +def unregister(): + bpy.utils.unregister_class(ExportDiffusionString) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) + +if __name__ == "__main__": + register()