import bpy
import bmesh
import os
from ..utils import exporter
from ..cAPI import SimulationAPI
import numpy as np
from .. import variables
from ..utils import caches

class HI_PHY_OT_StopSim(bpy.types.Operator):
    bl_idname = "hi_phy.stop_sim"
    bl_label = "Hi Phy Stop Simulation"
    bl_options = {'REGISTER'}
    active_solver: bpy.props.StringProperty(
        default='',
        name='Active Solver'
    )
    def execute(self, context):
        solver = context.scene.objects[self.active_solver]
        if solver.name in caches:
            caches[solver.name].driver.Interrupt();
        return {'FINISHED'}

class HI_PHY_OT_RunCurvesPreload(bpy.types.Operator):
    bl_idname = "hi_phy.preload_curves"
    bl_label = "Hi Phy Preload Curves"
    bl_options = {'REGISTER'}
    active_solver: bpy.props.StringProperty(
        default='',
        name='Active Solver'
    )
    def execute(self, context):
        variables.expression_attribute_live_update = True
        solver = context.scene.objects[self.active_solver]
        if not solver:
            self.report({"ERROR"}, "No valid hi phy solver")
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        for solver_name in caches :
            if (caches[solver_name].IsSimulationRunning()):
                self.report({"ERROR"}, "The simulation is still running")
                return {'CANCELLED'}
        # Enter object mode so we can get all the attributes
        bpy.ops.object.mode_set(mode='OBJECT')
        failed = False;
        # Gather all the objects
        curves = []
        constraints = []
        for obj in context.scene.objects:
            # curves
            if (obj.hi_phy.is_active and obj.hi_phy.object_type == "ELASTIC_ROD" and obj.hi_phy.solver and obj.hi_phy.solver.name == solver.name):
                if obj.type == 'CURVE' or obj.type == 'CURVES':
                    curves.append(obj)
                else :
                    self.report({"ERROR"}, "Only curve(s) are supported as elastic rod.")
                    failed = True
            # constraints
            if (obj.hi_phy.is_active and obj.hi_phy.object_type == "CONSTRAINT"):
                constraint_obj = obj.hi_phy.constraint_object0
                if (constraint_obj and constraint_obj.hi_phy.is_active and constraint_obj.hi_phy.solver.name == solver.name):
                    # We only care about constraints for the rods, and point/root constraints
                    if (constraint_obj.hi_phy.object_type != "ELASTIC_ROD" or
                        (obj.hi_phy.constraint_type != SimulationAPI.ConstraintType.RootConstraint and
                         obj.hi_phy.constraint_type != SimulationAPI.ConstraintType.PointConstraint and
                         obj.hi_phy.constraint_type != SimulationAPI.ConstraintType.RootConstraint)):
                        continue;
                    constraints.append(obj)

        scene = SimulationAPI.Scene("preload")
        depsgraph = bpy.context.evaluated_depsgraph_get()
        solver_eval = solver.evaluated_get(depsgraph)
        solver_node = exporter.ExportSolver(scene, solver_eval);
        if not exporter.ExportStaticAttributes(solver_node, solver_eval, self):
            self.report({"ERROR"}, ("Failed to export solver " + solver.name))
            failed = True
        # Set things to start frame
        frame = int(scene.start_time)
        context.scene.frame_set(frame)
        exporter.ExportTransform(solver_eval, solver_node, frame)
        if not exporter.ExportDynamicAttributes(solver_node, solver_eval, frame, self):
            self.report({"ERROR"}, ("Failed to export solver " + solver_eval.name + "."))
            failed = True

        # The first pass we export the static attributes (like topology)
        # Export elastic rods
        for obj in curves:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportCurvesTopology(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export curve " + obj.name + ". Please make sure it is a curve(s)"))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export curves " + obj.name))
                failed = True
                continue
            exporter.ExportTransform(obj_eval, node, frame)
            # We do not need to export rest shape, but we need to have it to know where to put the preload result
            if not obj.hi_phy.rest_shape:
                self.report({"ERROR"}, ("Curves " + obj.name + " has no rest shape to preload"))
                failed = True
                continue
            # export preload reference shape
            if not obj.hi_phy.preload_reference_shape:
                self.report({"ERROR"}, ("Failed to export curves " + obj.name + "'s preload reference shape"))
                failed = True
                continue
            reference_obj_eval = obj.hi_phy.preload_reference_shape.evaluated_get(depsgraph)
            # preload reference shape is static, export at 0 time
            if not exporter.ExportCurvesPosition(reference_obj_eval, node, 0, "preloadReferencePosition", "preloadReferenceFrameNormal"):
                self.report({"ERROR"}, ("Failed to export curves " + obj.name + " preload reference positions"))
                failed = True
                continue
            # export regular position
            if not exporter.ExportCurvesPosition(obj_eval, node, frame):
                self.report({"ERROR"}, ("Failed to export curves " + obj.name + " positions"))
                failed = True
                continue
            if not exporter.ExportDynamicAttributes(node, obj_eval, frame, self):
                self.report({"ERROR"}, ("Failed to export curve " + obj.name + " dynamic attributes"))
                failed = True
                continue

        # Export constraints
        for obj in constraints:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportConstraint(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export constraint " + obj.name + ". Please make sure it is a valid constraint"))
                failed = True
                continue
            if not exporter.ExportConstraintProperties(node, obj_eval):
                self.report({"ERROR"}, ("Failed to export constraint " + obj.name))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export constraint " + obj.name))
                failed = True
                continue

            exporter.ExportTransform(obj_eval, node, frame)
            if not exporter.ExportMeshPosition(obj_eval, node, frame):
                self.report({"ERROR"}, ("Failed to export constraint " + obj.name))
                failed = True
                continue
            if not exporter.ExportDynamicAttributes(node, obj_eval, frame, self):
                self.report({"ERROR"}, ("Failed to export constraint " + obj.name))
                failed = True
                continue

        if failed:
            self.report({"ERROR"}, ("Export has failed"))
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        driver = SimulationAPI.SimulationDriver();
        driver.Initialize(scene)
        driver.PreloadCurves(False)
        # Bring back the curves
        for obj in curves:
            obj_eval = obj.evaluated_get(depsgraph)
            node = scene.GetNode(obj_eval.name)
            position_accessor = scene.GetVector3DataAccessor(node, "position", frame + 1)
            rest_shape = obj.hi_phy.rest_shape

            position = rest_shape.data.attributes['position']
            n_verts = rest_shape.data.attributes.domain_size('POINT')
            if (position_accessor.GetDataArraySize() != n_verts):
                print ("simulation vertex data no longer matches the object " + node.name)
                continue;

            position_values = np.zeros((n_verts, 3), dtype=np.float32)
            position_accessor.CopyRawVector3ArrayToPython(position_values)
            position.data.foreach_set("vector", np.ravel(position_values))

        scene = None
        variables.expression_attribute_live_update = False
        return {'FINISHED'}

def GetLagrangianSimulationObjects(context, solver):
    affine_bodies = []
    curves = []
    cloths = []
    deformable_bodies = []
    soft_colliders = []
    colliders = []
    constraints = []
    force_fields = []
    for obj in context.scene.objects:
        if (not obj.hi_phy.is_active):
            continue
        # constraints are special, they don't have solver directly
        if (obj.hi_phy.object_type == "CONSTRAINT"):
            constraint_obj = obj.hi_phy.constraint_object0
            if (constraint_obj and constraint_obj.hi_phy.is_active and constraint_obj.hi_phy.solver and constraint_obj.hi_phy.solver.name == solver.name):
                other_obj = obj.hi_phy.constraint_object1
                if (other_obj and other_obj.hi_phy.is_active and other_obj.hi_phy.solver and other_obj.hi_phy.solver.name != solver.name):
                    # TODO: we probably could let user to bind a simulation object with another solver's collider. But it is a rare case.
                    self.report({"ERROR"}, "Two of the constraint " + obj.name + "'s objects are not associated with the same solver.")
                    failed = True
                else:
                    constraints.append(obj)
        if (not obj.hi_phy.solver or not obj.hi_phy.solver.name == solver.name):
            continue
        # affine bodies
        if (obj.hi_phy.object_type == "AFFINE_BODY"):
            if obj.type == 'MESH' :
                affine_bodies.append(obj)
            else :
                self.report({"ERROR"}, "Only triangle meshes are supported as affine bodies.").append(obj)
        # curves
        if (obj.hi_phy.object_type == "ELASTIC_ROD"):
            if obj.type == 'CURVE' or obj.type == 'CURVES':
                curves.append(obj)
            else :
                self.report({"ERROR"}, "Only curve(s) are supported as elastic rod.")
                failed = True
        # cloths
        if (obj.hi_phy.object_type == "CLOTH"):
            if obj.type == 'MESH' :
                cloths.append(obj)
            else :
                self.report({"ERROR"}, "Only triangle meshes are supported as cloth objects.").append(obj)
        # deformable_bodies
        if (obj.hi_phy.object_type == "DEFORMABLE_BODY"):
            if obj.type == 'MESH' :
                deformable_bodies.append(obj)
            else :
                self.report({"ERROR"}, "Only triangle meshes are supported as deformable body objects.").append(obj)
        # soft_colliders
        if (obj.hi_phy.object_type == "SOFT_COLLIDER"):
            if obj.type == 'MESH' :
                soft_colliders.append(obj)
            else :
                self.report({"ERROR"}, "Only triangle meshes are supported as soft colliders.").append(obj)
        # colliders
        if (obj.hi_phy.object_type == "COLLIDER"):
            if obj.type == 'MESH' :
                colliders.append(obj)
            else :
                self.report({"ERROR"}, "Only triangle meshes are supported as colliders.")
                failed = True
        # force fields
        if (obj.hi_phy.object_type == "FORCE_FIELD"):
            force_fields.append(obj)
    return affine_bodies, curves, cloths, deformable_bodies, soft_colliders, colliders, constraints, force_fields

def ExportLagrangianFrameData(context, solver, affine_bodies, curves, cloths, deformable_bodies, soft_colliders, colliders, constraints, force_fields, frame_start, frame_end, export_soft_collider_position_as_target, wm, scene, op):
    cache_path = bpy.path.abspath("//" + solver.hi_phy.cache_file)
    depsgraph = bpy.context.evaluated_depsgraph_get()
    failed = False;
    # export one more frame
    for frame in range(frame_start, frame_end + 1) :
        if wm:
            wm.progress_update(frame - frame_start)
        if (failed) :
            break;
        context.scene.frame_set(frame)
        solver_eval = solver.evaluated_get(depsgraph)
        solver_node = exporter.ExportSolver(scene, solver_eval);
        exporter.ExportTransform(solver_eval, solver_node, frame)
        if not exporter.ExportDynamicAttributes(solver_node, solver_eval, frame, op):
            self.report({"ERROR"}, ("Failed to export solver " + solver_eval.name + "."))
            failed = True
            continue
        exporter.ExportDeformableBodyMaterial(solver_eval, solver_node, frame);
        # affine bodies
        for obj in affine_bodies:
            obj_eval = obj.evaluated_get(depsgraph)
            node = scene.GetNode(obj_eval.name)
            if not node:
                self.report({"ERROR"}, ("Failed to export mesh " + obj.name + "."))
                failed = True
                continue
            exporter.ExportTransform(obj_eval, node, frame)
            # Only export position at the first frame
            if (frame == int(scene.start_time)):
                if not exporter.ExportMeshPosition(obj_eval, node, frame):
                    self.report({"ERROR"}, ("Failed to export affine body " + obj.name))
                    failed = True
                    continue
            if not exporter.ExportDynamicAttributes(node, obj_eval, frame, op):
                self.report({"ERROR"}, ("Failed to export affine body " + obj.name))
                failed = True
                continue
        # curves
        for obj in curves:
            obj_eval = obj.evaluated_get(depsgraph)
            node = scene.GetNode(obj_eval.name)
            if not node:
                self.report({"ERROR"}, ("Failed to export curves " + obj.name + "."))
                failed = True
                continue
            exporter.ExportTransform(obj_eval, node, frame)
            # export rest shape
            if obj.hi_phy.rest_shape:
                rest_obj_eval = obj.hi_phy.rest_shape.evaluated_get(depsgraph)
                if not exporter.ExportCurvesPosition(rest_obj_eval, node, frame, "restPosition", "restFrameNormal"):
                    self.report({"ERROR"}, ("Failed to export curves " + obj.name + " rest positions"))
                    failed = True
                    continue
            # export target shape
            if obj.hi_phy.target_shape:
                target_obj_eval = obj.hi_phy.target_shape.evaluated_get(depsgraph)
                if not exporter.ExportCurvesPosition(target_obj_eval, node, frame, "targetPosition", None):
                    self.report({"ERROR"}, ("Failed to export curves " + obj.name + " target positions"))
                    failed = True
                    continue
            # export regular position
            # Only export position at the first frame
            if (frame == int(scene.start_time)):
                if not exporter.ExportCurvesPosition(obj_eval, node, frame):
                    self.report({"ERROR"}, ("Failed to export curves " + obj.name + " positions"))
                    failed = True
                    continue
            if not exporter.ExportDynamicAttributes(node, obj_eval, frame, op):
                self.report({"ERROR"}, ("Failed to export curve " + obj.name + " dynamic attributes"))
                failed = True
                continue
        # cloths
        for obj in cloths:
            obj_eval = obj.evaluated_get(depsgraph)
            node = scene.GetNode(obj_eval.name)
            if not node:
                self.report({"ERROR"}, ("Failed to export mesh " + obj.name + "."))
                failed = True
                continue
            exporter.ExportTransform(obj_eval, node, frame)
            exporter.ExportClothMaterial(obj_eval, node, frame)
            # export rest shape
            if obj.hi_phy.rest_shape:
                rest_obj_eval = obj.hi_phy.rest_shape.evaluated_get(depsgraph)
                if not exporter.ExportMeshPosition(rest_obj_eval, node, frame, "restPosition"):
                    self.report({"ERROR"}, ("Failed to export cloth " + obj.name + " rest positions"))
                    failed = True
                    continue
            # export target shape
            if obj.hi_phy.target_shape:
                target_obj_eval = obj.hi_phy.target_shape.evaluated_get(depsgraph)
                if not exporter.ExportMeshPosition(target_obj_eval, node, frame, "targetPosition"):
                    self.report({"ERROR"}, ("Failed to export cloth " + obj.name + " target positions"))
                    failed = True
                    continue
            if not exporter.ExportClothUV(obj_eval, node, frame):
                self.report({"ERROR"}, ("Failed to export cloth " + obj.name + "'s uv"))
                failed = True
                continue
            # Only export position at the first frame
            if (frame == int(scene.start_time)):
                if not exporter.ExportMeshPosition(obj_eval, node, frame):
                    self.report({"ERROR"}, ("Failed to export cloth " + obj.name))
                    failed = True
                    continue
            if not exporter.ExportDynamicAttributes(node, obj_eval, frame, op):
                self.report({"ERROR"}, ("Failed to export cloth " + obj.name))
                failed = True
                continue
        # deformable_bodies
        for obj in deformable_bodies:
            obj_eval = obj.evaluated_get(depsgraph)
            node = scene.GetNode(obj_eval.name)
            if not node:
                self.report({"ERROR"}, ("Failed to export mesh " + obj.name + "."))
                failed = True
                continue
            exporter.ExportTransform(obj_eval, node, frame)
            # export target shape
            if obj.hi_phy.target_shape:
                target_obj_eval = obj.hi_phy.target_shape.evaluated_get(depsgraph)
                if not exporter.ExportMeshPosition(target_obj_eval, node, frame, "targetPosition"):
                    self.report({"ERROR"}, ("Failed to export deformable body " + obj.name + " target positions"))
                    failed = True
                    continue
            # export mesh bone
            # Those parameters will live on the mesh bone, so we need to exclude them from exporting
            mesh_bone_property_list = ["deformableBodyMeshBoneStiffness", "deformableBodyMeshBoneDamping"]
            if obj.hi_phy.mesh_bone:
                mesh_bone_eval = obj.hi_phy.mesh_bone.evaluated_get(depsgraph)
                if not exporter.ExportMeshPosition(mesh_bone_eval, node, frame, "deformableBodyMeshBonePosition"):
                    self.report({"ERROR"}, ("Failed to export deformable body " + obj.name + " mesh bone positions"))
                    failed = True
                    continue
                if not exporter.ExportDynamicAttributesFromList(mesh_bone_property_list, node, obj_eval, mesh_bone_eval, frame, op):
                    self.report({"ERROR"}, ("Failed to export deformable body " + obj.name))
                    failed = True
                    continue
            # Only export position at the first frame
            if (frame == int(scene.start_time)):
                if not exporter.ExportMeshPosition(obj_eval, node, frame):
                    self.report({"ERROR"}, ("Failed to export deformable body " + obj.name))
                    failed = True
                    continue
            if not exporter.ExportDynamicAttributes(node, obj_eval, frame, op, mesh_bone_property_list):
                self.report({"ERROR"}, ("Failed to export deformable body " + obj.name))
                failed = True
                continue
        # soft_colliders
        for obj in soft_colliders:
            obj_eval = obj.evaluated_get(depsgraph)
            node = scene.GetNode(obj_eval.name)
            if not node:
                self.report({"ERROR"}, ("Failed to export mesh " + obj.name + "."))
                failed = True
                continue
            exporter.ExportTransform(obj_eval, node, frame)
            # Only export position at the first frame
            if (frame == int(scene.start_time)):
                if not exporter.ExportMeshPosition(obj_eval, node, frame):
                    self.report({"ERROR"}, ("Failed to export soft collider " + obj.name))
                    failed = True
                    continue
            # export target shape
            if obj.hi_phy.target_shape:
                target_obj_eval = obj.hi_phy.target_shape.evaluated_get(depsgraph)
                if not exporter.ExportMeshPosition(target_obj_eval, node, frame, "targetPosition"):
                    self.report({"ERROR"}, ("Failed to export deformable body " + obj.name + " target positions"))
                    failed = True
                    continue
            else:
                # if no target shape, using regular position as target position
                # WARNING: this is incorrect for restart. If the frame has already been finished, the position is incorrect, so we have to pass a flag to disable it
                if export_soft_collider_position_as_target:
                    if not exporter.ExportMeshPosition(obj_eval, node, frame, 'targetPosition'):
                        self.report({"ERROR"}, ("Failed to export soft collider " + obj.name))
                        failed = True
                        continue
            if not exporter.ExportDynamicAttributes(node, obj_eval, frame, op):
                self.report({"ERROR"}, ("Failed to export soft collider " + obj.name))
                failed = True
                continue
        # Export colliders
        for obj in colliders:
            depsgraph = bpy.context.evaluated_depsgraph_get()
            obj_eval = obj.evaluated_get(depsgraph)
            node = scene.GetNode(obj_eval.name)
            if not node:
                self.report({"ERROR"}, ("Failed to export collider " + obj.name + "."))
                failed = True
                continue
            exporter.ExportTransform(obj_eval, node, frame)
            if not exporter.ExportMeshPosition(obj_eval, node, frame):
                self.report({"ERROR"}, ("Failed to export collider " + obj.name))
                failed = True
                continue
            if not exporter.ExportDynamicAttributes(node, obj_eval, frame, op):
                self.report({"ERROR"}, ("Failed to export collider " + obj.name))
                failed = True
                continue
        # Export Constraints
        for obj in constraints:
            depsgraph = bpy.context.evaluated_depsgraph_get()
            obj_eval = obj.evaluated_get(depsgraph)
            node = scene.GetNode(obj_eval.name)
            if not node:
                self.report({"ERROR"}, ("Failed to export constraint " + obj.name + "."))
                failed = True
                continue
            exporter.ExportTransform(obj_eval, node, frame)
            if not exporter.ExportConstraintProperties(node, obj_eval, frame):
                self.report({"ERROR"}, ("Failed to export constraint " + obj.name))
                failed = True
                continue
            if not exporter.ExportMeshPosition(obj_eval, node, frame):
                self.report({"ERROR"}, ("Failed to export constraint " + obj.name))
                failed = True
                continue
            if not exporter.ExportDynamicAttributes(node, obj_eval, frame, op):
                self.report({"ERROR"}, ("Failed to export constraint " + obj.name))
                failed = True
                continue
        # Export Force Fields
        for obj in force_fields:
            depsgraph = bpy.context.evaluated_depsgraph_get()
            obj_eval = obj.evaluated_get(depsgraph)
            node = scene.GetNode(obj_eval.name)
            if not node:
                self.report({"ERROR"}, ("Failed to export force field " + obj.name + "."))
                failed = True
                continue
            # export per object weights
            exporter.ExportForceFieldWeights(obj_eval, node, frame)
            exporter.ExportTransform(obj_eval, node, frame)
            if not exporter.ExportDynamicAttributes(node, obj_eval, frame, op):
                self.report({"ERROR"}, ("Failed to export force field " + obj.name))
                failed = True
                continue
        # If we are not in memory, we offload the frame (but leaving first frame)
        if (not solver.hi_phy.in_memeory and frame > scene.start_time):
            scene.UnloadFrame(cache_path, frame)
    return failed

class HI_PHY_OT_RunSim(bpy.types.Operator):
    bl_idname = "hi_phy.run_sim"
    bl_label = "Hi Phy Run Simulation"
    bl_options = {'REGISTER'}
    active_solver: bpy.props.StringProperty(
        default='',
        name='Active Solver'
    )
    def execute(self, context):
        variables.expression_attribute_live_update = True
        solver = context.scene.objects[self.active_solver]
        if not solver:
            self.report({"ERROR"}, "No valid hi phy solver")
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        for solver_name in caches :
            if (caches[solver_name].IsSimulationRunning()):
                self.report({"ERROR"}, "The simulation is still running")
                variables.expression_attribute_live_update = False
                return {'CANCELLED'}
        if solver.name in caches :
            # Set to start to remove deformation from the previous simulation
            caches[solver.name].SetToStartFrame()
            caches[solver.name].Clear()
        else :
            caches[solver.name] = exporter.Cache()
        cache_path = bpy.path.abspath("//" + solver.hi_phy.cache_file)
        # Enter object mode so we can get all the attributes
        bpy.ops.object.mode_set(mode='OBJECT')
        failed = False;
        # Gather all the objects
        affine_bodies, curves, cloths, deformable_bodies, soft_colliders, colliders, constraints, force_fields = GetLagrangianSimulationObjects(context, solver)

        scene = SimulationAPI.Scene("simulation")
        depsgraph = bpy.context.evaluated_depsgraph_get()
        solver_eval = solver.evaluated_get(depsgraph)
        solver_node = exporter.ExportSolver(scene, solver_eval);
        if not exporter.ExportStaticAttributes(solver_node, solver_eval, self):
            self.report({"ERROR"}, ("Failed to export solver " + solver.name))
            failed = True

        # The first pass we export the static attributes (like topology)
        # Export affine bodies
        for obj in affine_bodies:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportTriangleMeshTopology(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export mesh " + obj.name + ". Please make sure it is a triangle mesh"))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export affine body " + obj.name))
                failed = True
                continue
        # Export elastic rods
        for obj in curves:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportCurvesTopology(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export curve " + obj.name + ". Please make sure it is a curve(s)"))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export curves " + obj.name))
                failed = True
                continue
        # Export cloths
        for obj in cloths:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportTriangleMeshTopology(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export mesh " + obj.name + ". Please make sure it is a triangle mesh"))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export cloth" + obj.name))
                failed = True
                continue
        # Export deformable_bodies
        for obj in deformable_bodies:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportTriangleMeshTopology(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export mesh " + obj.name + ". Please make sure it is a triangle mesh"))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export deformable body" + obj.name))
                failed = True
                continue
        # Export soft_colliders
        for obj in soft_colliders:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportTriangleMeshTopology(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export mesh " + obj.name + ". Please make sure it is a triangle mesh"))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export soft collider" + obj.name))
                failed = True
                continue
        # Export colliders
        for obj in colliders:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportTriangleMeshTopology(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export mesh " + obj.name + ". Please make sure it is a triangle mesh"))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export collider " + obj.name))
                failed = True
                continue
        # Export constraints
        for obj in constraints:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportConstraint(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export constraint " + obj.name + ". Please make sure it is a valid constraint"))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export constraint " + obj.name))
                failed = True
                continue
        # Export force fields
        for obj in force_fields:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportForceField(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export force field " + obj.name + ". Please make sure it is a valid force field with all the influenced objects valid simulation objects from the same solver."))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export force field " + obj.name))
                failed = True
                continue

        if failed:
            self.report({"ERROR"}, ("Export has failed"))
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}

        #Second pass, export the timeline
        wm = context.window_manager
        wm.progress_begin(0, int(scene.end_time + 1) - int(scene.start_time))
        failed = ExportLagrangianFrameData(context, solver, affine_bodies, curves, cloths, deformable_bodies, soft_colliders, colliders, constraints, force_fields, int(scene.start_time), int(scene.end_time), True, wm, scene, self)
        wm.progress_end()
        if failed:
            self.report({"ERROR"}, ("Export has failed"))
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        caches[solver.name].SetScene(scene)
        caches[solver.name].driver = SimulationAPI.SimulationDriver();
        scene.ResetSimulationProgress();
        SimulationAPI.SimulationDriver.RunSimulationAsync(caches[solver.name].driver, scene, solver.hi_phy.in_memeory, cache_path)
        # move scene to the start time
        context.scene.frame_set(int(scene.start_time))
        scene = None
        variables.expression_attribute_live_update = False
        #utils.force_dirty_scene()
        return {'FINISHED'}

class HI_PHY_OT_ResumeSim(bpy.types.Operator):
    bl_idname = "hi_phy.resume_sim"
    bl_label = "Hi Phy Resume Simulation"
    bl_options = {'REGISTER'}
    active_solver: bpy.props.StringProperty(
        default='',
        name='Active Solver'
    )
    def execute(self, context):
        resume_frame = bpy.context.scene.frame_current
        variables.expression_attribute_live_update = True
        solver = context.scene.objects[self.active_solver]
        if not solver:
            self.report({"ERROR"}, "No valid hi phy solver")
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        for solver_name in caches :
            if (caches[solver_name].IsSimulationRunning()):
                self.report({"ERROR"}, "The simulation is still running")
                variables.expression_attribute_live_update = False
                return {'CANCELLED'}
        if not solver.name in caches:
            self.report({"ERROR"}, "No simulation cache exsits")
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        cache = caches[solver.name]
        if not cache.driver:
            cache.driver = SimulationAPI.SimulationDriver();
        cache_path = bpy.path.abspath("//" + solver.hi_phy.cache_file)
        # Enter object mode so we can get all the attributes
        bpy.ops.object.mode_set(mode='OBJECT')
        failed = False;
        # Gather all the objects
        affine_bodies, curves, cloths, deformable_bodies, soft_colliders, colliders, constraints, force_fields = GetLagrangianSimulationObjects(context, solver)
        scene = cache.scene
        start_frame = solver.hi_phy.frame_range_simulation_start;
        end_frame = solver.hi_phy.frame_range_simulation_end;
        simulate_end_frame = scene.simulation_end_time;
        if (start_frame != int(scene.start_time)):
            self.report({"ERROR"}, "Start time has changed")
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        if (resume_frame > int(scene.simulation_end_time)):
            self.report({"ERROR"}, "The last simulated frame is: " + str(scene.simulation_end_time))
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        if (resume_frame > end_frame):
            self.report({"ERROR"}, "The end of the simulation is: " + str(end_frame))
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}

        scene.TruncateSimulation(resume_frame)
        # We can not re-export static variables. It will cause inconsistency in the cache
        # Only export the dynamic attributes
        # export the current frame without exporting the position for soft collider target
        failed = ExportLagrangianFrameData(context, solver, affine_bodies, curves, cloths, deformable_bodies, soft_colliders, colliders, constraints, force_fields, resume_frame, resume_frame, True, None, scene, self)
        if failed:
            self.report({"ERROR"}, ("Export has failed"))
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        wm = context.window_manager
        wm.progress_begin(0, int(scene.end_time + 1) - int(resume_frame + 1))
        # Don't set object position for the solver, the result is no longer valid
        solver.hi_phy.solver_is_exporting = True;
        failed = ExportLagrangianFrameData(context, solver, affine_bodies, curves, cloths, deformable_bodies, soft_colliders, colliders, constraints, force_fields, resume_frame + 1, int(scene.end_time), True, wm, scene, self)
        solver.hi_phy.solver_is_exporting = False;
        wm.progress_end()
        if failed:
            self.report({"ERROR"}, ("Export has failed"))
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        SimulationAPI.SimulationDriver.RunSimulationAsync(caches[solver.name].driver, scene, solver.hi_phy.in_memeory, cache_path)
        # move scene back to start time
        context.scene.frame_set(resume_frame)
        scene = None
        variables.expression_attribute_live_update = False
        #utils.force_dirty_scene()
        return {'FINISHED'}

class HI_PHY_OT_RunMPMSim(bpy.types.Operator):
    bl_idname = "hi_phy.run_mpm_sim"
    bl_label = "Hi Phy Run MPM Simulation"
    bl_options = {'REGISTER'}
    active_solver: bpy.props.StringProperty(
        default='',
        name='Active Solver'
    )
    def execute(self, context):
        variables.expression_attribute_live_update = True
        solver = context.scene.objects[self.active_solver]
        if not solver:
            self.report({"ERROR"}, "No valid hi phy solver")
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        for solver_name in caches :
            if (caches[solver_name].IsSimulationRunning()):
                self.report({"ERROR"}, "The simulation is still running")
                variables.expression_attribute_live_update = False
                return {'CANCELLED'}
        if solver.name in caches :
            # Set to start to remove deformation from the previous simulation
            caches[solver.name].SetToStartFrame()
            caches[solver.name].Clear()
        else :
            caches[solver.name] = exporter.Cache()
        cache_path = bpy.path.abspath("//" + solver.hi_phy.cache_file)
        # Enter object mode so we can get all the attributes
        bpy.ops.object.mode_set(mode='OBJECT')
        failed = False;
        # Gather all the objects
        meshless_particles = []
        colliders = []
        for obj in context.scene.objects:
            if (not obj.hi_phy.is_active or not obj.hi_phy.solver or not obj.hi_phy.solver.name == solver.name):
                continue;
            # meshless particles
            if (obj.hi_phy.object_type == "MPM_MESHLESS_PARTICLES"):
                if obj.type == 'MESH' :
                    meshless_particles.append(obj)
                else :
                    self.report({"ERROR"}, "Only vertex only meshes are supported as meshless particles objects.").append(obj)
            # colliders
            if (obj.hi_phy.object_type == "COLLIDER"):
                if obj.type == 'MESH' :
                    colliders.append(obj)
                else :
                    self.report({"ERROR"}, "Only triangle meshes are supported as colliders objects.").append(obj)

        scene = SimulationAPI.Scene("simulation")
        depsgraph = bpy.context.evaluated_depsgraph_get()
        solver_eval = solver.evaluated_get(depsgraph)
        solver_node = exporter.ExportSolver(scene, solver_eval);
        if not exporter.ExportStaticAttributes(solver_node, solver_eval, self):
            self.report({"ERROR"}, ("Failed to export solver " + solver.name))
            failed = True

        # The first pass we export the static attributes (like topology)
        # Export meshless particles
        for obj in meshless_particles:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportPointsTopology(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export mesh " + obj.name + ". Please make sure it is a point mesh"))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export meshless particles " + obj.name))
                failed = True
                continue

        # Export colliders
        for obj in colliders:
            obj_eval = obj.evaluated_get(depsgraph)
            node = exporter.ExportTriangleMeshTopology(scene, None, obj_eval, obj_eval.name);
            if not node:
                self.report({"ERROR"}, ("Failed to export mesh " + obj.name + ". Please make sure it is a triangle mesh"))
                failed = True
                continue
            if not exporter.ExportStaticAttributes(node, obj_eval, self):
                self.report({"ERROR"}, ("Failed to export collider " + obj.name))
                failed = True
                continue

        #Second pass, export the timeline
        wm = context.window_manager
        wm.progress_begin(0, int(scene.end_time + 1) - int(scene.start_time))
        for frame in range(int(scene.start_time), int(scene.end_time + 1)) :
            wm.progress_update(frame - int(scene.start_time))
            if (failed) :
                break;
            context.scene.frame_set(frame)
            solver_eval = solver.evaluated_get(depsgraph)
            exporter.ExportTransform(solver_eval, solver_node, frame)
            if not exporter.ExportDynamicAttributes(solver_node, solver_eval, frame, self):
                self.report({"ERROR"}, ("Failed to export solver " + solver_eval.name + "."))
                failed = True
                continue

            # meshless particles
            for obj in meshless_particles:
                obj_eval = obj.evaluated_get(depsgraph)
                node = scene.GetNode(obj_eval.name)
                if not node:
                    self.report({"ERROR"}, ("Failed to export points " + obj.name + "."))
                    failed = True
                    continue

                exporter.ExportTransform(obj_eval, node, frame)
                if not exporter.ExportMeshPosition(obj_eval, node, frame):
                    self.report({"ERROR"}, ("Failed to export meshless particle " + obj.name))
                    failed = True
                    continue
                if not exporter.ExportDynamicAttributes(node, obj_eval, frame, self):
                    self.report({"ERROR"}, ("Failed to export meshless particle " + obj.name))
                    failed = True
                    continue

            # Export colliders
            for obj in colliders:
                depsgraph = bpy.context.evaluated_depsgraph_get()
                obj_eval = obj.evaluated_get(depsgraph)
                node = scene.GetNode(obj_eval.name)
                if not node:
                    self.report({"ERROR"}, ("Failed to export collider " + obj.name + "."))
                    failed = True
                    continue
                exporter.ExportTransform(obj_eval, node, frame)
                if not exporter.ExportMeshPosition(obj_eval, node, frame):
                    self.report({"ERROR"}, ("Failed to export collider " + obj.name))
                    failed = True
                    continue
                if not exporter.ExportDynamicAttributes(node, obj_eval, frame, self):
                    self.report({"ERROR"}, ("Failed to export collider " + obj.name))
                    failed = True
                    continue
            # If we are not in memory, we offload the frame (but leaving first frame)
            if (not solver.hi_phy.in_memeory and frame > scene.start_time + 1):
                scene.UnloadFrame(cache_path, frame)

        wm.progress_end()
        if failed:
            self.report({"ERROR"}, ("Export has failed"))
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        caches[solver.name].SetScene(scene)
        caches[solver.name].driver = SimulationAPI.SimulationDriver();
        scene.ResetSimulationProgress();
        SimulationAPI.SimulationDriver.RunMPMSimulationAsync(caches[solver.name].driver, scene, solver.hi_phy.in_memeory, cache_path)
        # move scene to the start time
        context.scene.frame_set(int(scene.start_time))
        scene = None
        variables.expression_attribute_live_update = False
        #utils.force_dirty_scene()
        return {'FINISHED'}

class HI_PHY_OT_ResumeMPMSim(bpy.types.Operator):
    bl_idname = "hi_phy.resume_mpm_sim"
    bl_label = "Hi Phy Resume MPM Simulation"
    bl_options = {'REGISTER'}
    active_solver: bpy.props.StringProperty(
        default='',
        name='Active Solver'
    )
    def execute(self, context):
        resume_frame = bpy.context.scene.frame_current
        variables.expression_attribute_live_update = True
        solver = context.scene.objects[self.active_solver]
        if not solver:
            self.report({"ERROR"}, "No valid hi phy solver")
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        for solver_name in caches :
            if (caches[solver_name].IsSimulationRunning()):
                self.report({"ERROR"}, "The simulation is still running")
                variables.expression_attribute_live_update = False
                return {'CANCELLED'}
        if not solver.name in caches:
            self.report({"ERROR"}, "No simulation cache exsits")
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        cache = caches[solver.name]
        if not cache.driver:
            cache.driver = SimulationAPI.SimulationDriver();
        cache_path = bpy.path.abspath("//" + solver.hi_phy.cache_file)
        # Enter object mode so we can get all the attributes
        bpy.ops.object.mode_set(mode='OBJECT')
        failed = False;
        # Gather all the objects
        meshless_particles = []
        colliders = []
        for obj in context.scene.objects:
            if (not obj.hi_phy.is_active or not obj.hi_phy.solver or not obj.hi_phy.solver.name == solver.name):
                continue;
            # meshless particles
            if (obj.hi_phy.object_type == "MPM_MESHLESS_PARTICLES"):
                if obj.type == 'MESH' :
                    meshless_particles.append(obj)
                else :
                    self.report({"ERROR"}, "Only vertex only meshes are supported as meshless particles objects.").append(obj)
            # colliders
            if (obj.hi_phy.object_type == "COLLIDER"):
                if obj.type == 'MESH' :
                    colliders.append(obj)
                else :
                    self.report({"ERROR"}, "Only triangle meshes are supported as colliders objects.").append(obj)

        scene = cache.scene
        start_frame = solver.hi_phy.frame_range_simulation_start;
        end_frame = solver.hi_phy.frame_range_simulation_end;
        simulate_end_frame = scene.simulation_end_time;
        if (start_frame != int(scene.start_time)):
            self.report({"ERROR"}, "Start time has changed")
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        if (resume_frame > int(scene.simulation_end_time)):
            self.report({"ERROR"}, "The last simulated frame is: " + str(scene.simulation_end_time))
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        if (resume_frame > end_frame):
            self.report({"ERROR"}, "The end of the simulation is: " + str(end_frame))
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        # Use this to change frame range
        depsgraph = bpy.context.evaluated_depsgraph_get()
        solver_eval = solver.evaluated_get(depsgraph)
        solver_node = exporter.ExportSolver(scene, solver_eval);
        scene.TruncateSimulation(resume_frame)
        depsgraph = bpy.context.evaluated_depsgraph_get()
        # export the timeline
        wm = context.window_manager
        wm.progress_begin(0, int(end_frame + 1) - int(resume_frame))
        # Don't set object position for the solver, the result is no longer valid
        solver.hi_phy.solver_is_exporting = True;
        for frame in range(int(resume_frame + 1), int(end_frame + 1)) :
            wm.progress_update(frame - int(resume_frame))
            if (failed) :
                break;
            context.scene.frame_set(frame)
            solver_eval = solver.evaluated_get(depsgraph)
            exporter.ExportTransform(solver_eval, solver_node, frame)
            if not exporter.ExportDynamicAttributes(solver_node, solver_eval, frame, self):
                self.report({"ERROR"}, ("Failed to export solver " + solver_eval.name + "."))
                failed = True
                continue

            # meshless particles
            for obj in meshless_particles:
                obj_eval = obj.evaluated_get(depsgraph)
                node = scene.GetNode(obj_eval.name)
                if not node:
                    self.report({"ERROR"}, ("Failed to export points " + obj.name + "."))
                    failed = True
                    continue

                exporter.ExportTransform(obj_eval, node, frame)
                if not exporter.ExportMeshPosition(obj_eval, node, frame):
                    self.report({"ERROR"}, ("Failed to export meshless particle " + obj.name))
                    failed = True
                    continue
                if not exporter.ExportDynamicAttributes(node, obj_eval, frame, self):
                    self.report({"ERROR"}, ("Failed to export meshless particle " + obj.name))
                    failed = True
                    continue

            # Export colliders
            for obj in colliders:
                depsgraph = bpy.context.evaluated_depsgraph_get()
                obj_eval = obj.evaluated_get(depsgraph)
                node = scene.GetNode(obj_eval.name)
                if not node:
                    self.report({"ERROR"}, ("Failed to export collider " + obj.name + "."))
                    failed = True
                    continue
                exporter.ExportTransform(obj_eval, node, frame)
                if not exporter.ExportMeshPosition(obj_eval, node, frame):
                    self.report({"ERROR"}, ("Failed to export collider " + obj.name))
                    failed = True
                    continue
                if not exporter.ExportDynamicAttributes(node, obj_eval, frame, self):
                    self.report({"ERROR"}, ("Failed to export collider " + obj.name))
                    failed = True
                    continue
            # If we are not in memory, we offload the frame (but leaving first frame)
            if (not solver.hi_phy.in_memeory and frame > scene.start_time + 1):
                scene.UnloadFrame(cache_path, frame)
        solver.hi_phy.solver_is_exporting = False;
        wm.progress_end()
        if failed:
            self.report({"ERROR"}, ("Export has failed"))
            variables.expression_attribute_live_update = False
            return {'CANCELLED'}
        SimulationAPI.SimulationDriver.RunMPMSimulationAsync(caches[solver.name].driver, scene, solver.hi_phy.in_memeory, cache_path)
        # move scene to the start time
        context.scene.frame_set(resume_frame)
        scene = None
        variables.expression_attribute_live_update = False
        #utils.force_dirty_scene()
        return {'FINISHED'}

__CLASSES__ = [
    HI_PHY_OT_RunSim,
    HI_PHY_OT_ResumeSim,
    HI_PHY_OT_RunMPMSim,
    HI_PHY_OT_ResumeMPMSim,
    HI_PHY_OT_StopSim,
    HI_PHY_OT_RunCurvesPreload
]

def register():
    for class_ in __CLASSES__:
        bpy.utils.register_class(class_)

def unregister():
    for class_ in reversed(__CLASSES__):
        bpy.utils.unregister_class(class_)
