Guest User

bake_operators

a guest
Jun 7th, 2018
85
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 21.74 KB | None | 0 0
  1. # Blender FLIP Fluid Add-on
  2. # Copyright (C) 2018 Ryan L. Guy
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  16.  
  17. import bpy, os, glob, json, threading
  18.  
  19. from .. import bake
  20. from ..objects import flip_fluid_mesh_exporter
  21. from .. import export
  22.  
  23.  
  24. class BakeData(object):
  25.     def __init__(self):
  26.         self.reset()
  27.  
  28.     def reset(self):
  29.         dprops = bpy.context.scene.flip_fluid.get_domain_properties()
  30.  
  31.         self.progress = 0.0
  32.         self.completed_frames = 0
  33.         self.is_finished = False
  34.         self.is_initialized = False
  35.         self.is_cancelled = False
  36.         self.is_safe_to_exit = True
  37.         self.is_console_output_enabled = True
  38.         if dprops is not None:
  39.             self.is_console_output_enabled = dprops.debug.display_console_output
  40.         self.error_message = ""
  41.  
  42.  
  43. class BakeFluidSimulation(bpy.types.Operator):
  44.     bl_idname = "flip_fluid_operators.bake_fluid_simulation"
  45.     bl_label = "Bake Fluid Simulation"
  46.     bl_description = "Run fluid simulation"
  47.     bl_options = {'REGISTER'}
  48.  
  49.  
  50.     def __init__(self):
  51.         self.timer = None
  52.         self.thread = None
  53.         self.is_export_operator_launched = False
  54.         self.is_thread_launched = False
  55.         self.is_thread_finished = False
  56.         self.is_updating_status = False
  57.         self.data = BakeData()
  58.  
  59.  
  60.     def _get_domain_properties(self):
  61.         return bpy.context.scene.flip_fluid.get_domain_properties()
  62.  
  63.  
  64.     def _reset_bake(self, context):
  65.         dprops = self._get_domain_properties()
  66.         dprops.bake.is_simulation_running = True
  67.         dprops.bake.bake_progress = 0.0
  68.         dprops.bake.num_baked_frames = 0
  69.         dprops.stats.refresh_stats()
  70.         self.data.reset()
  71.  
  72.  
  73.     def _delete_cache_file(self, filepath):
  74.         try:
  75.             os.remove(filepath)
  76.         except OSError:
  77.             pass
  78.  
  79.  
  80.     def _initialize_domain_properties_frame_range(self, context):
  81.         dprops = self._get_domain_properties()
  82.         dprops.simulation.frame_start = context.scene.frame_start
  83.         dprops.simulation.frame_end = context.scene.frame_end
  84.  
  85.  
  86.     def _initialize_domain(self, context):
  87.         dprops = self._get_domain_properties()
  88.         self._initialize_domain_properties_frame_range(context)
  89.         dprops.mesh_cache.reset_cache_objects()
  90.  
  91.  
  92.     def _launch_thread(self):
  93.         dprops = self._get_domain_properties()
  94.         cache_directory = dprops.cache.get_cache_abspath()
  95.         export_filepath = os.path.join(cache_directory, dprops.bake.export_filename)
  96.         self.data.progress = 0.0
  97.         self.thread = threading.Thread(target = bake.bake,
  98.                                        args = (export_filepath, cache_directory, self.data,))
  99.         self.thread.start()
  100.  
  101.  
  102.     def _update_stats(self, context):
  103.         dprops = self._get_domain_properties()
  104.         cache_dir = dprops.cache.get_cache_abspath()
  105.         statsfilepath = os.path.join(cache_dir, dprops.stats.stats_filename)
  106.         if not os.path.isfile(statsfilepath):
  107.             with open(statsfilepath, 'w') as f:
  108.                 f.write(json.dumps({}, sort_keys=True, indent=4))
  109.  
  110.         temp_dir = os.path.join(cache_dir, "temp")
  111.         match_str = "framestats" + "[0-9]"*6 + ".data"
  112.         stat_files = glob.glob(os.path.join(temp_dir, match_str))
  113.         if not stat_files:
  114.             return
  115.  
  116.         with open(statsfilepath, 'r') as f:
  117.             stats_dict = json.loads(f.read())
  118.  
  119.         for statpath in stat_files:
  120.             filename = os.path.basename(statpath)
  121.             frameno = int(filename[len("framestats"):-len(".data")])
  122.             with open(statpath, 'r') as frame_stats:
  123.                 try:
  124.                     frame_stats_dict = json.loads(frame_stats.read())
  125.                 except:
  126.                     # stats data may not be finished writing which could
  127.                     # result in a decode error. Skip this data for now and
  128.                     # process the next time stats are updated.
  129.                     continue
  130.                 stats_dict[str(frameno)] = frame_stats_dict
  131.             self._delete_cache_file(statpath)
  132.  
  133.         with open(statsfilepath, 'w') as f:
  134.                 f.write(json.dumps(stats_dict, sort_keys=True, indent=4))
  135.  
  136.         dprops.stats.is_stats_current = False
  137.         context.scene.flip_fluid_helper.frame_complete_callback()
  138.  
  139.  
  140.     def _update_status(self, context):
  141.         if self.thread and not self.thread.is_alive():
  142.             self.is_thread_finished = True
  143.             self.thread = None
  144.  
  145.             if self.data.error_message:
  146.                 bpy.ops.flip_fluid_operators.display_error(
  147.                     'INVOKE_DEFAULT',
  148.                     error_message="Error Baking Fluid Simulation",
  149.                     error_description=self.data.error_message,
  150.                     popup_width=400
  151.                     )
  152.  
  153.         self._update_stats(context)
  154.  
  155.         dprops = self._get_domain_properties()
  156.         dprops.bake.is_bake_initialized = self.data.is_initialized
  157.         dprops.bake.bake_progress = self.data.progress
  158.         dprops.bake.num_baked_frames = self.data.completed_frames
  159.         dprops.bake.is_safe_to_exit = self.data.is_safe_to_exit
  160.         self.data.is_cancelled = dprops.bake.is_bake_cancelled
  161.         try:
  162.             # Depending on window, area may be None
  163.             context.area.tag_redraw()
  164.         except:
  165.             pass
  166.  
  167.  
  168.     def _cancel_bake(self, context):
  169.         if self.is_thread_finished:
  170.             return
  171.         dprops = self._get_domain_properties()
  172.         dprops.bake.is_bake_cancelled = True
  173.         self._update_status(context)
  174.  
  175.  
  176.     def _is_export_file_available(self):
  177.         dprops = self._get_domain_properties()
  178.         cache_directory = dprops.cache.get_cache_abspath()
  179.         export_filepath = os.path.join(cache_directory, dprops.bake.export_filename)
  180.         return os.path.isfile(export_filepath)
  181.  
  182.  
  183.     @classmethod
  184.     def poll(cls, context):
  185.         dprops = bpy.context.scene.flip_fluid.get_domain_properties()
  186.         if dprops is None:
  187.             return False
  188.         return not dprops.bake.is_simulation_running
  189.  
  190.  
  191.     def modal(self, context, event):
  192.         dprops = self._get_domain_properties()
  193.         if dprops.simulation.settings_export_mode == 'EXPORT_DEFAULT':
  194.             is_exporting = not dprops.bake.is_autosave_available
  195.         elif dprops.simulation.settings_export_mode == 'EXPORT_SKIP':
  196.             is_exporting = not self._is_export_file_available()
  197.         elif dprops.simulation.settings_export_mode == 'EXPORT_FORCE':
  198.             is_exporting = True
  199.  
  200.         if not self.is_export_operator_launched and is_exporting:
  201.             bpy.ops.flip_fluid_operators.export_fluid_simulation("INVOKE_DEFAULT")
  202.             self.is_export_operator_launched = True
  203.  
  204.         if dprops.bake.is_export_operator_running and is_exporting:
  205.             return {'PASS_THROUGH'}
  206.  
  207.         if not self.is_thread_launched and dprops.bake.is_bake_cancelled:
  208.             self.cancel(context)
  209.             return {'FINISHED'}
  210.  
  211.         if not self.is_thread_launched:
  212.             self._launch_thread()
  213.             self.is_thread_launched = True
  214.  
  215.         if event.type == 'TIMER' and not self.is_updating_status:
  216.             self.is_updating_status = True
  217.             self._update_status(context)
  218.             self.is_updating_status = False
  219.  
  220.         if self.is_thread_finished:
  221.             self.cancel(context)
  222.             return {'FINISHED'}
  223.  
  224.         return {'PASS_THROUGH'}
  225.  
  226.  
  227.     def execute(self, context):
  228.         if not context.scene.flip_fluid.is_domain_object_set():
  229.             self.report({"ERROR_INVALID_INPUT"},
  230.                          "Fluid simulation requires a domain object")
  231.             self.cancel(context)
  232.             return {'CANCELLED'}
  233.  
  234.         if context.scene.flip_fluid.get_num_domain_objects() > 1:
  235.             self.report({"ERROR_INVALID_INPUT"},
  236.                         "There must be only one domain object")
  237.             self.cancel(context)
  238.             return {'CANCELLED'}
  239.  
  240.         dprops = self._get_domain_properties()
  241.         if dprops.bake.is_simulation_running:
  242.             self.cancel(context)
  243.             return {'CANCELLED'}
  244.  
  245.         dprops.cache.mark_cache_directory_set()
  246.         self._reset_bake(context)
  247.         self._initialize_domain(context)
  248.  
  249.         context.window_manager.modal_handler_add(self)
  250.         self.timer = context.window_manager.event_timer_add(0.1, context.window)
  251.  
  252.         return {'RUNNING_MODAL'}
  253.  
  254.  
  255.     def cancel(self, context):
  256.         if self.timer:
  257.             context.window_manager.event_timer_remove(self.timer)
  258.             self.timer = None
  259.  
  260.         dprops = self._get_domain_properties()
  261.         if dprops is None:
  262.             return
  263.  
  264.         dprops.bake.is_simulation_running = False
  265.         dprops.bake.is_bake_cancelled = False
  266.         dprops.bake.check_autosave()
  267.         try:
  268.             # Depending on window, area may be None
  269.             context.area.tag_redraw()
  270.         except:
  271.             pass
  272.  
  273.  
  274. class BakeFluidSimulationCommandLine(bpy.types.Operator):
  275.     bl_idname = "flip_fluid_operators.bake_fluid_simulation_cmd"
  276.     bl_label = "Bake Fluid Simulation"
  277.     bl_description = "Bake fluid simulation from command line"
  278.     bl_options = {'REGISTER'}
  279.  
  280.  
  281.     def __init__(self):
  282.         self.thread = None
  283.         self.mesh_data = {}
  284.         self.data = BakeData()
  285.         self.mesh_exporter = None
  286.  
  287.  
  288.     def _get_domain_properties(self):
  289.         return bpy.context.scene.flip_fluid.get_domain_properties()
  290.  
  291.  
  292.     def _reset_bake(self, context):
  293.         dprops = self._get_domain_properties()
  294.         dprops.bake.is_simulation_running = True
  295.         dprops.bake.bake_progress = 0.0
  296.         dprops.bake.num_baked_frames = 0
  297.         dprops.stats.refresh_stats()
  298.         self.data.reset()
  299.  
  300.  
  301.     def _initialize_domain_properties_frame_range(self, context):
  302.         dprops = self._get_domain_properties()
  303.         dprops.simulation.frame_start = context.scene.frame_start
  304.         dprops.simulation.frame_end = context.scene.frame_end
  305.  
  306.  
  307.     def _initialize_domain(self, context):
  308.         dprops = self._get_domain_properties()
  309.         self._initialize_domain_properties_frame_range(context)
  310.         dprops.mesh_cache.reset_cache_objects()
  311.  
  312.  
  313.     def _initialize_mesh_exporter(self, context):
  314.         simprops = context.scene.flip_fluid
  315.         objects = (simprops.get_fluid_objects() +
  316.                    simprops.get_obstacle_objects() +
  317.                    simprops.get_inflow_objects() +
  318.                    simprops.get_outflow_objects())
  319.         object_names = [obj.name for obj in objects]
  320.  
  321.         self.mesh_data = {key: None for key in object_names}
  322.         self.mesh_exporter = flip_fluid_mesh_exporter.MeshExporter(self.mesh_data)
  323.  
  324.  
  325.     def _get_logfile_name(self, context):
  326.         dprops = self._get_domain_properties()
  327.         cache_directory = dprops.cache.get_cache_abspath()
  328.         logs_directory = os.path.join(cache_directory, "logs")
  329.  
  330.         basename = os.path.basename(bpy.data.filepath)
  331.         basename = os.path.splitext(basename)[0]
  332.         if not basename:
  333.             basename = "untitled"
  334.  
  335.         filename = basename
  336.         filepath = os.path.join(logs_directory, filename + ".txt")
  337.         if os.path.isfile(filepath):
  338.             for i in range(1, 1000):
  339.                 filename = basename + "." + str(i).zfill(3)
  340.                 filepath = os.path.join(logs_directory, filename + ".txt")
  341.                 if not os.path.isfile(filepath):
  342.                     break;
  343.  
  344.         return filename + ".txt"
  345.  
  346.  
  347.     def _initialize_export_operator(self, context):
  348.         dprops = self._get_domain_properties()
  349.         dprops.bake.is_export_operator_cancelled = False
  350.         dprops.bake.is_export_operator_running = True
  351.         dprops.bake.export_progress = 0.0
  352.         dprops.bake.export_stage = 'STATIC'
  353.         dprops.cache.logfile_name = self._get_logfile_name(context)
  354.  
  355.  
  356.     def _export_simulation_data_file(self):
  357.         dprops = self._get_domain_properties()
  358.         dprops.bake.export_filepath = os.path.join(dprops.cache.get_cache_abspath(),
  359.                                                    dprops.bake.export_filename)
  360.         dprops.bake.export_success = export.export(
  361.                 bpy.context,
  362.                 self.mesh_data,
  363.                 dprops.bake.export_filepath
  364.                 )
  365.  
  366.         if dprops.bake.export_success:
  367.             dprops.bake.is_cache_directory_set = True
  368.  
  369.  
  370.     def _export_simulation_data(self, context):
  371.         print("Exporting simulation data...")
  372.         dprops = self._get_domain_properties()
  373.         if dprops.simulation.settings_export_mode == 'EXPORT_DEFAULT':
  374.             is_exporting = not dprops.bake.is_autosave_available
  375.         elif dprops.simulation.settings_export_mode == 'EXPORT_SKIP':
  376.             is_exporting = not self._is_export_file_available()
  377.         elif dprops.simulation.settings_export_mode == 'EXPORT_FORCE':
  378.             is_exporting = True
  379.  
  380.         self._initialize_mesh_exporter(context)
  381.         self._initialize_export_operator(context)
  382.  
  383.         while True:
  384.             is_finished = self.mesh_exporter.update_export(3600)
  385.             dprops.bake.export_progress = self.mesh_exporter.export_progress
  386.             dprops.bake.export_stage = self.mesh_exporter.export_stage
  387.             if is_finished:
  388.                 if self.mesh_exporter.is_error:
  389.                     self.report({"ERROR"}, self.mesh_exporter.error_message)
  390.                     dprops.bake.is_bake_cancelled = True
  391.                     dprops.bake.is_export_operator_running = False
  392.                     return
  393.  
  394.                 self._export_simulation_data_file()
  395.                 dprops.bake.is_export_operator_running = False
  396.                 return
  397.  
  398.  
  399.     def _delete_cache_file(self, filepath):
  400.         try:
  401.             os.remove(filepath)
  402.         except OSError:
  403.             pass
  404.  
  405.  
  406.     def _update_simulation_stats(self, context):
  407.         dprops = self._get_domain_properties()
  408.         cache_dir = dprops.cache.get_cache_abspath()
  409.         statsfilepath = os.path.join(cache_dir, dprops.stats.stats_filename)
  410.         if not os.path.isfile(statsfilepath):
  411.             with open(statsfilepath, 'w') as f:
  412.                 f.write(json.dumps({}, sort_keys=True, indent=4))
  413.  
  414.         temp_dir = os.path.join(cache_dir, "temp")
  415.         match_str = "framestats" + "[0-9]"*6 + ".data"
  416.         stat_files = glob.glob(os.path.join(temp_dir, match_str))
  417.         if not stat_files:
  418.             return
  419.  
  420.         with open(statsfilepath, 'r') as f:
  421.             stats_dict = json.loads(f.read())
  422.  
  423.         for statpath in stat_files:
  424.             filename = os.path.basename(statpath)
  425.             frameno = int(filename[len("framestats"):-len(".data")])
  426.             with open(statpath, 'r') as frame_stats:
  427.                 try:
  428.                     frame_stats_dict = json.loads(frame_stats.read())
  429.                 except:
  430.                     # stats data may not be finished writing which could
  431.                     # result in a decode error. Skip this data for now and
  432.                     # process the next time stats are updated.
  433.                     continue
  434.                 stats_dict[str(frameno)] = frame_stats_dict
  435.             self._delete_cache_file(statpath)
  436.  
  437.         with open(statsfilepath, 'w') as f:
  438.                 f.write(json.dumps(stats_dict, sort_keys=True, indent=4))
  439.  
  440.         dprops.stats.is_stats_current = False
  441.  
  442.  
  443.     def _run_fluid_simulation(self, context):
  444.         print("Running fluid simulation..")
  445.         dprops = self._get_domain_properties()
  446.         cache_directory = dprops.cache.get_cache_abspath()
  447.         export_filepath = os.path.join(cache_directory, dprops.bake.export_filename)
  448.         self.data.progress = 0.0
  449.         self.data.is_console_output_enabled = True
  450.         bake.bake(export_filepath, cache_directory, self.data)
  451.         self._update_simulation_stats(context)
  452.  
  453.  
  454.     def execute(self, context):
  455.         if not context.scene.flip_fluid.is_domain_object_set():
  456.             self.report({"ERROR_INVALID_INPUT"},
  457.                          "Fluid simulation requires a domain object")
  458.             self.cancel(context)
  459.             return {'CANCELLED'}
  460.  
  461.         if context.scene.flip_fluid.get_num_domain_objects() > 1:
  462.             self.report({"ERROR_INVALID_INPUT"},
  463.                         "There must be only one domain object")
  464.             self.cancel(context)
  465.             return {'CANCELLED'}
  466.  
  467.         self._reset_bake(context)
  468.         self._initialize_domain(context)
  469.         self._export_simulation_data(context)
  470.  
  471.         dprops = self._get_domain_properties()
  472.         if dprops.bake.is_bake_cancelled:
  473.             self.cancel(context)
  474.             return {'FINISHED'}
  475.  
  476.         self._run_fluid_simulation(context)
  477.         self.cancel(context)
  478.  
  479.         return {'FINISHED'}
  480.  
  481.  
  482.     def cancel(self, context):
  483.         dprops = self._get_domain_properties()
  484.         if dprops is None:
  485.             return
  486.         dprops.bake.is_simulation_running = False
  487.         dprops.bake.is_bake_cancelled = False
  488.         dprops.bake.is_export_operator_running = False
  489.         dprops.bake.check_autosave()
  490.  
  491.  
  492. class CancelBakeFluidSimulation(bpy.types.Operator):
  493.     bl_idname = "flip_fluid_operators.cancel_bake_fluid_simulation"
  494.     bl_label = "Cancel Bake Fluid Simulation"
  495.     bl_description = "Stop baking fluid simulation"
  496.  
  497.     @classmethod
  498.     def poll(cls, context):
  499.         dprops = bpy.context.scene.flip_fluid.get_domain_properties()
  500.         if dprops is None:
  501.             return False
  502.         return not dprops.bake.is_bake_cancelled
  503.  
  504.  
  505.     def execute(self, context):
  506.         dprops = bpy.context.scene.flip_fluid.get_domain_properties()
  507.         if dprops is None:
  508.             return {'CANCELLED'}
  509.  
  510.         dprops.bake.is_bake_cancelled = True
  511.         dprops.bake.is_export_operator_cancelled = True
  512.  
  513.         return {'FINISHED'}
  514.  
  515.  
  516. class FlipFluidResetBake(bpy.types.Operator):
  517.     bl_idname = "flip_fluid_operators.reset_bake"
  518.     bl_label = "Reset Bake"
  519.     bl_description = ("Reset simulation bake to initial state. WARNING: this" +
  520.                       " operation will delete previously baked simulation data.")
  521.  
  522.  
  523.     def _delete_cache_file(self, filepath):
  524.         try:
  525.             os.remove(filepath)
  526.         except OSError:
  527.             pass
  528.  
  529.  
  530.     def _delete_cache_directory(self, directory, extension):
  531.         if not os.path.isdir(directory):
  532.             return
  533.        
  534.         for f in os.listdir(directory):
  535.             if f.endswith(extension):
  536.                 self._delete_cache_file(os.path.join(directory, f))
  537.  
  538.         if len(os.listdir(directory)) == 0:
  539.             os.rmdir(directory)
  540.  
  541.  
  542.     def _clear_cache(self, context):
  543.         dprops = bpy.context.scene.flip_fluid.get_domain_properties()
  544.         cache_dir = dprops.cache.get_cache_abspath()
  545.  
  546.         statsfilepath = os.path.join(cache_dir, dprops.stats.stats_filename)
  547.         self._delete_cache_file(statsfilepath)
  548.  
  549.         bakefiles_dir = os.path.join(cache_dir, "bakefiles")
  550.         self._delete_cache_directory(bakefiles_dir, ".bbox")
  551.         self._delete_cache_directory(bakefiles_dir, ".bobj")
  552.         self._delete_cache_directory(bakefiles_dir, ".wwp")
  553.         self._delete_cache_directory(bakefiles_dir, ".fpd")
  554.  
  555.         temp_dir = os.path.join(cache_dir, "temp")
  556.         self._delete_cache_directory(temp_dir, ".data")
  557.  
  558.         savestates_dir = os.path.join(cache_dir, "savestates")
  559.         autosave_dir = os.path.join(savestates_dir, "autosave")
  560.         self._delete_cache_directory(autosave_dir, ".state")
  561.         self._delete_cache_directory(autosave_dir, ".data")
  562.         self._delete_cache_directory(savestates_dir, ".state")
  563.         self._delete_cache_directory(savestates_dir, ".data")
  564.  
  565.  
  566.     @classmethod
  567.     def poll(cls, context):
  568.         dprops = bpy.context.scene.flip_fluid.get_domain_properties()
  569.         if dprops is None:
  570.             return False
  571.         return not dprops.bake.is_simulation_running
  572.  
  573.  
  574.     def execute(self, context):
  575.         dprops = bpy.context.scene.flip_fluid.get_domain_properties()
  576.         cache_path = dprops.cache.get_cache_abspath()
  577.         if not os.path.isdir(cache_path):
  578.             self.report({"ERROR"}, "Current cache directory does not exist")
  579.             return {'CANCELLED'}
  580.         dprops.cache.mark_cache_directory_set()
  581.  
  582.         self._clear_cache(context)
  583.         dprops.mesh_cache.reset_cache_objects()
  584.        
  585.         self.report({"INFO"}, "Successfully reset bake")
  586.  
  587.         dprops.stats.refresh_stats()
  588.         dprops.stats.reset_time_remaining()
  589.         dprops.bake.check_autosave()
  590.         dprops.render.reset_bake()
  591.  
  592.         return {'FINISHED'}
  593.  
  594.  
  595.     def invoke(self, context, event):
  596.         return context.window_manager.invoke_confirm(self, event)
  597.  
  598.  
  599. def register():
  600.     bpy.utils.register_class(BakeFluidSimulation)
  601.     bpy.utils.register_class(BakeFluidSimulationCommandLine)
  602.     bpy.utils.register_class(CancelBakeFluidSimulation)
  603.     bpy.utils.register_class(FlipFluidResetBake)
  604.  
  605.  
  606. def unregister():
  607.     bpy.utils.unregister_class(BakeFluidSimulation)
  608.     bpy.utils.unregister_class(BakeFluidSimulationCommandLine)
  609.     bpy.utils.unregister_class(CancelBakeFluidSimulation)
  610.     bpy.utils.unregister_class(FlipFluidResetBake)
Add Comment
Please, Sign In to add comment