bl_info = {
    "name": "Solar System Simulator",
    "description": "Creates solarsystem, simulates orbit around sun and keyframes planets.",
    "author": "Ludwig Boge, Jonatan Ebenholm, Nikita Sidarovich, Berkay Orhan",
    "version": (1, 0),
    "blender": (4, 0, 0),
    "location": "n panel > Solarsystem Simulator",
    "warning": "", # used for warning icon and text in addons panel
    "support": "COMMUNITY",
    "category": "Add System",
}

# Lägg till skalningsfaktorer, skapa solen
import bpy, os
import ntpath
import numpy as np
import time
import random
import math
from glob import glob
from bpy.types import PropertyGroup, UIList, Panel, Operator
from bpy.props import CollectionProperty, PointerProperty, FloatProperty, StringProperty, IntProperty

# NOTE: Potential bugg när flera planeter i listan heter samma sak. Objektet som skapas heter då planet.001 .002 osv, matchar alltså inte
# namnet i listan, lösning är att göra så att planeterna måste ha unika namn i listan. Denna bugg kan skapa problem när man ska keyframa objekten
numberOfPlanets = 0 # fixed

# lista över planeterna och deras värden i format [namn, x, y, radius, mass, startHastighet], index 0 är merkurius, 1 är venus osv. Startvärden för x,y
# är i aphelion (e+9) m. radie är i Km. massa är i kg (e+24). starthastighet e3
planetPresets = [['Mercury', 69.8, 0, 2, 0.330, 58.98], ['Venus', 108.9, 0, 3, 4.87, 35.26], ['Earth', 152.1, 0, 5, 5.97, 30.29], ['Mars', 249.3, 0, 4, 0.642, 26.50], ['Jupiter', 816.4, 0, 15, 1898, 13.72], ['Saturn', 1506.5, 0, 10, 568, 10.18], ['Uranus', 3001.4, 0, 6, 86.8, 7.11], ['Neptune', 4558.9, 0, 6, 102, 5.50]]
#bpy.data.images.load("C:/Users/johnt/Dropbox/PC/Desktop/Blender/Bleender Projekt/2k_mercury.jpg", check_existing=True)
#mats = [['Mercury', bpy.data.materials.new(name = 'Mer_Mat') ], ['Venus', bpy.data.materials.new(name='Ven_Mat')], ['Earth', bpy.data.materials.new(name='Ear_Mat' )], ['Mars', bpy.data.materials.new(name='Mar_Mat')], ['Jupiter', bpy.data.materials.new(name='Jup_Mat')], ['Saturn', bpy.data.materials.new(name='Sat_Mat')], ['Uranus', bpy.data.materials.new(name='Ura_Mat')], ['Neptune', bpy.data.materials.new(name='Nep_Mat')]]

disableUI = False
advancedSimulation = False
simulateMoons = False
# DisableUI blir true när simuleringen körs, just nu är det en cheap lösning, borde vara att UI finns kvar men blir grayed out,
# Skulle vara bra om man kan även se progress för simuleringen

# Global variables
G = 6.67430e-11 # Gravitational constant in m^3 kg^-1 s^-2  
M_sun = 1.9889e30 # Sun's mass in kg

# PlanetProperties är en mall för varje objekt i my_list
class PlanetProperties(PropertyGroup):
    name: StringProperty(
        name="Name",
        description="The name of the planet",
        default="Unnamed planet"
    )
    mass: FloatProperty(
        name="Mass (e+24) kg",
        description="The mass of the planet",
        default=1.0,
        min=0.01,
        max=100000.0,
    )
    aphelion: FloatProperty(
        name="Aphelion (e+9) m",
        description="The furthest distance away from the sun in the planet's orbit",
        default=1.0,
        min=0.01,
        max=10000.0,
    )
    radius: FloatProperty(
        name="Radius",
        description="The radius of the planet",
        default=1.0,
        min=0.01,
        max=300000.0,
    )
    initialVelocity: FloatProperty(
        name="Initial Velocity",
        description="The initial velocity of the planet",
        default=1.0,
        min=0.01,
        max=300000.0,
    )
    

class MY_UL_List(UIList):
    
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
        
        custom_icon = 'WORLD'
        
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            layout.label(text=item.name, icon = custom_icon)

        elif self.layout_type in {'GRID'}:
            layout.alignment = 'CENTER'
            layout.label(text="", icon = custom_icon)
            
    #end draw_item
    
#end MY_UL_List

class LIST_OT_NewItem(Operator):
    """Add a new planet to the system."""
    
    bl_idname = "my_list.new_item"
    bl_label = "Add a new item"
    
    def execute(self, context):
        global numberOfPlanets
        newPlanet = context.scene.my_list.add()
        newPlanet.name += "." + str(numberOfPlanets)
        numberOfPlanets += 1
        return{'FINISHED'}
    #end execute
    
#end

class LIST_OT_DeleteItem(Operator):
    """Delete the selected planet from the system."""
    
    bl_idname = "my_list.delete_item"
    bl_label = "Deletes an item"
    
    @classmethod
    def poll(cls, context):
        return context.scene.my_list
    
    def execute(self, context):
        my_list = context.scene.my_list
        index = context.scene.list_index
        
        my_list.remove(index)
        context.scene.list_index = min(max(0, index - 1), len(my_list) - 1)
        
        return{'FINISHED'}
    #end execute
    
#end

class LIST_OT_NewPreset(Operator):
    """Add a new preset planet to the system."""
    
    bl_idname = "my_list.new_preset"
    bl_label = "Add a new preset planet"
    
    planet_choices = [
        ('0', 'Mercury', 'Create Mercury'),
        ('1', 'Venus', 'Create Venus'),
        ('2', 'Earth', 'Create Earth'),
        ('3', 'Mars', 'Create Mars'),
        ('4', 'Jupiter', 'Create Jupiter'),
        ('5', 'Saturn', 'Create Saturn'),
        ('6', 'Uranus', 'Create Uranus'),
        ('7', 'Neptune', 'Create Neptune')
    ]
    
    planet_enum: bpy.props.EnumProperty(
        name="Planets",
        description="Choose a planet to create",
        items=planet_choices
    )
    
    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self)
        
    def draw(self, context):
        layout = self.layout
        layout.prop(self, "planet_enum")
    
    def execute(self, context):
        item = context.scene.my_list.add()
        item.name = planetPresets[int(self.planet_enum)][0]
        item.aphelion = planetPresets[int(self.planet_enum)][1]
        item.radius = planetPresets[int(self.planet_enum)][3]
        item.mass = planetPresets[int(self.planet_enum)][4]
        item.initialVelocity = planetPresets[int(self.planet_enum)][5]
        return{'FINISHED'}
    #end execute
    
#end

class SolarSystem(Panel):
    """Creates a Panel in the scene context of the properties editor"""
    bl_label = "Solar System"
    bl_idname = "PT_SolarSystem"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Solarsystem Simulator'

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        
        if disableUI == False:
            row = layout.row()
            
            if len(scene.my_list) > 0:
              row.operator("mesh.create_planets")  
            
            row = layout.row() 
            row.template_list("MY_UL_List", "The_List", scene, "my_list", scene, "list_index")
            
            row = layout.row()
            row.operator('my_list.new_item', text='New')
            row.operator('my_list.delete_item', text='Remove')
            row = layout.row()
            row.operator('my_list.new_preset', text='Add planet preset')
            
            if scene.list_index >= 0 and scene.my_list:
                item = scene.my_list[scene.list_index]
                
                row = layout.row()
                row.prop(item, "name")
                row = layout.row()
                row.prop(item, "mass")
                row = layout.row()
                row.prop(item, "aphelion")
                row = layout.row()
                row.prop(item, "radius")
            
            
            row = layout.row()
            row.prop(scene, "scale_factor") # borde funka
            row = layout.row()
            row = layout.row()
            row = layout.row()
            row = layout.row()
            row = layout.row()
            row = layout.row()
            
            collectionFound = False
            for myCol in bpy.data.collections:
                if myCol.name == "Solar System":
                    collectionFound = True
                    break
            row.prop(scene, "sim_time")
            row = layout.row()
            row.enabled = collectionFound
            row.operator('mesh.simulate_system', text='Run Simulation')
            
            row = layout.row()
            row = layout.row()
            row = layout.row()
            row = layout.row()
            row = layout.row()
            row.prop(scene, "sim_mode", slider=True)
            row.enabled = collectionFound
        else:
            row = layout.row()
            row.label(text="Running simulation, please wait")
        #end draw
        
#end SolarSystem
            
class CreatePlanets(Operator) :
    """Creates the planet objects in a collection"""
    
    bl_idname = "mesh.create_planets"
    bl_label = "Create planets"
    bl_options = {"UNDO"}
    
    
    
    def invoke(self, context, event) :
        # Om användaren redan har skapat en collection med planeter, ta bort och skapa nya planeter i den collectionen för att uppdatera planeterna
        collectionFound = False
        filepath = bpy.utils.user_resource("SCRIPTS") + "\\addons"
        filename_list = glob(os.path.join(filepath,"*.jpg"))
        
        
            
        for myCol in bpy.data.collections:
            if myCol.name == "Solar System":
                collectionFound = True
                break
        
        if collectionFound:
            # Selectar alla object i collectionen och sedan tar bort dem
            for obj in bpy.data.collections['Solar System'].all_objects:
                obj.select_set(True)
                
            
            bpy.ops.object.delete()
           
            for block in bpy.data.meshes:
                if block.users == 0:
                    bpy.data.meshes.remove(block)
            for block in bpy.data.materials:
                if block.users == 0:
                    bpy.data.materials.remove(block)
            for block in bpy.data.images:
                if block.users == 0:
                    bpy.data.images.remove(block)           
            
        else:    
            # Skapar en ny collection
            collection = bpy.data.collections.new("Solar System")
            bpy.context.scene.collection.children.link(collection)
        collection = bpy.data.collections['Solar System']
        # Skapar planeterna i collectionen
        for item in context.scene.my_list:
            bpy.ops.mesh.primitive_uv_sphere_add(location=(item.aphelion*bpy.context.scene.scale_factor*100, 0, 0), radius=(item.radius))
            new_mat = bpy.data.materials.new(name = item.name)
            new_mat.use_nodes = True
            bsdf = new_mat.node_tree.nodes["Principled BSDF"]
            noImage = True
            for planet_name in filename_list:
                
                if item.name + ".jpg" == path_leaf(planet_name):
                    texImage = new_mat.node_tree.nodes.new('ShaderNodeTexImage')
                    texImage.image = bpy.data.images.load(planet_name)
                    new_mat.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color'])
                    noImage = False
            if noImage:
                #Material för planeter som inte finns med i preseten
                color_ramp = new_mat.node_tree.nodes.new('ShaderNodeValToRGB')
                noise_tex = new_mat.node_tree.nodes.new('ShaderNodeTexNoise')
                mapping = new_mat.node_tree.nodes.new('ShaderNodeMapping')
                mix = new_mat.node_tree.nodes.new('ShaderNodeMixRGB')
                mussgrave_tex = new_mat.node_tree.nodes.new("ShaderNodeTexNoise")
                tex_coord = new_mat.node_tree.nodes.new('ShaderNodeTexCoord')
                new_mat.node_tree.links.new(bsdf.inputs['Base Color'],color_ramp.outputs['Color'] )
                new_mat.node_tree.links.new(color_ramp.inputs['Fac'],mix.outputs['Color'] )
                new_mat.node_tree.links.new(mix.inputs['Color1'], mussgrave_tex.outputs[0] )
                new_mat.node_tree.links.new(mix.inputs['Color2'], noise_tex.outputs[0])
                new_mat.node_tree.links.new(noise_tex.inputs['Vector'], mapping.outputs['Vector'])
                new_mat.node_tree.links.new(mussgrave_tex.inputs['Vector'], mapping.outputs['Vector'])
                new_mat.node_tree.links.new(mapping.inputs['Vector'], tex_coord.outputs['Generated'])
                new_mat.node_tree.nodes['Noise Texture'].inputs['Scale'].default_value = 15.0
                new_mat.node_tree.nodes['Noise Texture'].inputs['Detail'].default_value = 15.0
                new_mat.node_tree.nodes['Noise Texture'].inputs['Scale'].default_value = 50.0
                new_mat.node_tree.nodes['Noise Texture'].inputs['Detail'].default_value = 16.0
                new_mat.node_tree.nodes[5].inputs['Fac'].default_value = 0.4
                color_ramp.color_ramp.elements.remove(color_ramp.color_ramp.elements[0])
                color_ramp.color_ramp.elements.new(0.250)
                color_ramp.color_ramp.elements.new(0.440)
                color_ramp.color_ramp.elements.new(0.600)
                color_ramp.color_ramp.interpolation = "CARDINAL"
                color_ramp.color_ramp.elements[0].color = (random.uniform(0,1),random.uniform(0,1)
                ,random.uniform(0,1),1)
                color_ramp.color_ramp.elements[1].color = (random.uniform(0,1),random.uniform(0,1)
                ,random.uniform(0,1),1)
                color_ramp.color_ramp.elements[2].color = (random.uniform(0,1),random.uniform(0,1)
                ,random.uniform(0,1),1)
                color_ramp.color_ramp.elements.remove(color_ramp.color_ramp.elements[3])
                
                
            
            obj = bpy.context.active_object
            obj.name = item.name
            bpy.ops.object.shade_smooth()
            context.object.active_material = new_mat

            bpy.ops.collection.objects_remove_all()
            collection.objects.link(obj)
        bpy.ops.mesh.primitive_uv_sphere_add(location=(0,0,0), radius = (30))
        #Skapar solmaterial.
        new_mat = bpy.data.materials.new(name = 'Sun')
        #Sätter igång noder.
        new_mat.use_nodes = True
        #Tar bort pricipled BSDF.
        new_mat.node_tree.nodes.remove(new_mat.node_tree.nodes['Principled BSDF'])
        #Skapar noder i shadern.
        emission = new_mat.node_tree.nodes.new('ShaderNodeEmission')
        material_output = new_mat.node_tree.nodes['Material Output']
        mix_shader = new_mat.node_tree.nodes.new('ShaderNodeMixShader')
        transparent = new_mat.node_tree.nodes.new('ShaderNodeBsdfTransparent')
        light_path = new_mat.node_tree.nodes.new('ShaderNodeLightPath')
        color_ramp = new_mat.node_tree.nodes.new('ShaderNodeValToRGB')
        noise_tex = new_mat.node_tree.nodes.new('ShaderNodeTexNoise')
        mapping = new_mat.node_tree.nodes.new('ShaderNodeMapping')
        mix = new_mat.node_tree.nodes.new('ShaderNodeMixRGB')
        mussgrave_tex = new_mat.node_tree.nodes.new('ShaderNodeTexNoise')
        tex_coord = new_mat.node_tree.nodes.new('ShaderNodeTexCoord')
        #Kopplar noders outputs till andras inputs.
        new_mat.node_tree.links.new(material_output.inputs['Surface'], mix_shader.outputs['Shader'])
        new_mat.node_tree.links.new(mix_shader.inputs[1], emission.outputs['Emission'])
        new_mat.node_tree.links.new(mix_shader.inputs[2], transparent.outputs['BSDF'])
        new_mat.node_tree.links.new(mix_shader.inputs[0], light_path.outputs['Is Diffuse Ray'])
        new_mat.node_tree.links.new(emission.inputs['Color'], color_ramp.outputs['Color'])
        new_mat.node_tree.links.new(color_ramp.inputs['Fac'], noise_tex.outputs['Fac'])
        new_mat.node_tree.links.new(noise_tex.inputs['Vector'], mapping.outputs['Vector'])
        new_mat.node_tree.links.new(mapping.inputs['Vector'],mix.outputs['Color'])
        new_mat.node_tree.links.new(mix.inputs['Color1'],mussgrave_tex.outputs[0])
        new_mat.node_tree.links.new(mix.inputs['Color2'],tex_coord.outputs['Generated'])
        new_mat.node_tree.links.new(mussgrave_tex.inputs['Vector'],tex_coord.outputs['Generated'])
        #Ändrar värden på vissa nodes attribut.
        new_mat.node_tree.nodes['Noise Texture'].inputs['Scale'].default_value = 50.0
        new_mat.node_tree.nodes['Noise Texture'].inputs['Detail'].default_value = 16.0
        new_mat.node_tree.nodes[8].inputs['Fac'].default_value = 0.9
        new_mat.node_tree.nodes['Noise Texture'].inputs['Scale'].default_value = 20.0
        new_mat.node_tree.nodes['Noise Texture'].inputs['Detail'].default_value = 16.0
        new_mat.node_tree.nodes['Noise Texture'].inputs['Scale'].default_value = 50.0
        new_mat.node_tree.nodes['Emission'].inputs['Strength'].default_value = 8
        #Fixar till ColorRampen så att den har points i rätt delar 
        #samt fixar deras färger och sätter rampen till cardinal.
        color_ramp.color_ramp.elements.remove(color_ramp.color_ramp.elements[0])
        color_ramp.color_ramp.elements.new(0.250)
        color_ramp.color_ramp.elements.new(0.440)
        color_ramp.color_ramp.elements.new(0.600)
        color_ramp.color_ramp.elements.new(0.720)
        color_ramp.color_ramp.elements.new(0.900)
        color_ramp.color_ramp.interpolation = "CARDINAL"
        color_ramp.color_ramp.elements[0].color = (0,0,0,1)
        color_ramp.color_ramp.elements[1].color = (0.973445,0.283148,0.043735,1)
        color_ramp.color_ramp.elements[2].color = (0.846873,0.783538,0.174648,1)
        color_ramp.color_ramp.elements[3].color = (1,0.076185,0.08022,1)
        color_ramp.color_ramp.elements[4].color = (0.947306,0.658374,0.017642,1)
        color_ramp.color_ramp.elements.remove(color_ramp.color_ramp.elements[5])
        obj = bpy.context.active_object
        obj.name = "Sun"
        bpy.ops.object.shade_smooth()
        #Sätter materialet till solen.
        context.object.active_material = new_mat
        bpy.ops.collection.objects_remove_all()
        collection.objects.link(obj)
        return {"FINISHED"}
    #end invoke

#end CreatePlanets

class SimulateSystem(Operator) :
    """Runs the simulation of the orbits. This may take a while"""
    
    bl_idname = "mesh.simulate_system"
    bl_label = "Simulate System"

    def invoke(self, context, event) :
        global disableUI
        disableUI = True
        
        planets = []
        
        h = 300  # Time step in seconds (10 minutes)
        print(bpy.context.scene.sim_time)
        total_simulation_time =math.ceil(bpy.context.scene.sim_time)*3600*24*365
        num_timesteps = total_simulation_time // h  # Total number of timesteps
        store_every = 100  # Store the position every 100 steps

        for item in context.scene.my_list:
            # Callar constructorn Planet() i simuleringskoden
            planets.append(Planet(item.name, item.mass*(10**24), [str(item.aphelion)+"e9", 0, 0], [0, str(item.initialVelocity)+"e3"],))
            
        print("started simulating")
        start_time = time.time()
        positions = simulate_orbits(planets, h, num_timesteps, G, M_sun, store_every)
        disableUI = False
        self.report({'INFO'}, "--- Simulation took %s seconds ---" % (time.time() - start_time))
        
        for obj in bpy.data.collections['Solar System'].all_objects: # I denna loop ska keyframing ske
            # obj är objekt som ska keyframas efter simulering
            # Ur "positions" kan positionerna fås i form av en size(3) array genom "positions.namn[0...1000..osv]"

            #nu kommer värden att altid bindas som earths värde, fixa genom  att kolla vilket obj vi är på
            frame_num = 0
            for planet, plan_pos in positions.items(): 
                    if obj.name == planet:
                        for pos in plan_pos:
                           obj.location = pos*bpy.context.scene.scale_factor /(10**7)
                           obj.keyframe_insert(data_path = 'location', frame=frame_num)
                           frame_num = frame_num + 1

        return {"FINISHED"}
    #end invoke

#end SimulateSystem

#-----------------------------------Simulation Code----------------------------------------------------------------------------------------

class CelestialBody:
    def __init__(self, name, mass, initial_pos, initial_vel):
        self.name = name
        self.mass = mass
        self.position = np.array(initial_pos, dtype=float)
        
        temp = np.sqrt((G*M_sun)/float(initial_pos[0])) # Beräknar unik initialhastiget för custom planeter
        
        self.velocity = np.array([0, temp, 0], dtype=float)
        self.acceleration = np.zeros(3, dtype=float)  # 3D vector with zeros

    def update_position(self, h):
        self.position += h * self.velocity

    def update_velocity(self, h):
        self.velocity += h * self.acceleration

    def reset_acceleration(self):
        self.acceleration = np.zeros(3)  # Reset to 3D zero vector

    def calculate_suns_gravitational_acceleration(self, G, M_sun):
        r_vector = -self.position  # Assuming Sun is at the origin
        r = np.linalg.norm(r_vector)
        if r > 0:
            acceleration_magnitude = G * M_sun / (r**2)
            acceleration_vector = acceleration_magnitude * (r_vector / r)
            return acceleration_vector
        return np.zeros(3)  # Return 3D zero vector
    
    def calculate_inter_gravitational_forces(self, other, G):
        if self is other:
            return
        r_vector = other.position - self.position
        r = np.linalg.norm(r_vector)
        if r > 0:
            force_magnitude = G  * other.mass / (r**2)
            force_vector = force_magnitude * (r_vector / r)
            acceleration_vector = force_vector
            self.acceleration += acceleration_vector

class Planet(CelestialBody):
    def __init__(self, name, mass, initial_pos, initial_vel):
        super().__init__(name, mass, initial_pos, initial_vel)
        self.moons = []

    def add_moon(self, moon):
        self.moons.append(moon)


class Moon(CelestialBody):
    def __init__(self, name, mass, initial_pos, initial_vel, planet):
        super().__init__(name, mass, initial_pos, initial_vel)
        self.planet = planet  # The planet this moon orbits around

moons = [
    #Moon("Moon", 7.342e22, [147.1e9 + 384.4e6, 0, 0], [0, 30.29e3 + 1.022e3, 0], planets[2]),
    
]

#--------------------------------- Simulation -------------------------------#

# Time array
#h = 300  # Time step in seconds
#t = np.arange(365 * 24 * 3600)  # Time array, 1 year in minutes
        
#planets[2].moons.append(moons[0]) # Adding Moon to Earth  

      

def path_leaf(path):
    head, tail = ntpath.split(path)
    return tail or ntpath.basename(head)


# Simulation function
def simulate_orbits(planets, h, num_timesteps, G, M_sun, store_every):
    # store positions in dictionaries
    positions = {body.name: [] for planet in planets for body in [planet] + planet.moons}
    
    for step in range(num_timesteps):

        # Reset acceleration and apply gravitational forces
        for planet in planets:
            planet.reset_acceleration()

            # Sun's gravitational effect on planets
            sun_acceleration = planet.calculate_suns_gravitational_acceleration(G, M_sun)
            planet.acceleration += sun_acceleration
            
            if simulateMoons == True:
                for moon in planet.moons:
                    moon.reset_acceleration()
                    #print(vars(moon)) # debugging

                    # Gravitational effect of the Sun on the moon
                    moon_acceleration_sun = moon.calculate_suns_gravitational_acceleration(G, M_sun)
                    moon.acceleration += moon_acceleration_sun
                    
                    # Gravitational effect of the planet on its moon
                    moon.calculate_inter_gravitational_forces(planet, G)
                    # Gravitational effect of the moon on its planet
                    planet.calculate_inter_gravitational_forces(moon, G)

        if bpy.context.scene.sim_mode:
            # Inter-planet gravitational forces
            for i, planet1 in enumerate(planets):
                for planet2 in planets[i+1:]:
                    planet1.calculate_inter_gravitational_forces(planet2, G)
                    planet2.calculate_inter_gravitational_forces(planet1, G)
    

         # Update positions and velocities for planets and moons
        for planet in planets:
            planet.update_velocity(h)
            planet.update_position(h)
            
            if simulateMoons == True:
                for moon in planet.moons:
                    moon.update_velocity(h)
                    moon.update_position(h)
                
        # Store positions at specified intervals
        if step % store_every == 0:
            for planet in planets:
                positions[planet.name].append(planet.position.copy())
                
                if simulateMoons == True:
                    for moon in planet.moons:
                        positions[moon.name].append(moon.position.copy())
                    
                
    # We are converting them to NumPy arrays at the end because it 
    # is more efficient to append to lists than to NumPy arrays.
    for key in positions:
        positions[key] = np.array(positions[key])

    # 'key' iterates through each key in the positions dictionary. 
    # These keys are the names of the celestial bodies (Planet or Moon) 
    # that were added to the dictionary earlier in the simulation.
        
    return positions

#--------------------------------------------------------------------------------------------------------------------

def register():
    bpy.utils.register_class(PlanetProperties)
    
    bpy.types.Scene.my_list = CollectionProperty(type = PlanetProperties)
    bpy.types.Scene.list_index = IntProperty(name = "Index for my_list", default = 0)
    
    bpy.utils.register_class(MY_UL_List)
    bpy.utils.register_class(LIST_OT_NewItem)
    bpy.utils.register_class(LIST_OT_DeleteItem)
    bpy.utils.register_class(LIST_OT_NewPreset)
    bpy.utils.register_class(SolarSystem)
    bpy.utils.register_class(CreatePlanets)
    bpy.utils.register_class(SimulateSystem)
    bpy.types.Scene.planet_enum = bpy.props.EnumProperty(
        name="Planets",
        description="Choose a planet",
        items=LIST_OT_NewPreset.planet_choices
    )
    bpy.types.Scene.scale_factor = bpy.props.FloatProperty(
        name="Scale factor",
        description="The factor at which the size of the system will be scaled for visualization",
        default=0.01,
        min=0.001,
        max=1.0
    )
    bpy.types.Scene.sim_time = bpy.props.FloatProperty(
        name="Simulation time",
        description = "The amount of years that will be simulated, will always be rounded up to a whole number",
        default =1.5,
        min = 0.1,
        max=500,
    )
    bpy.types.Scene.sim_mode = bpy.props.BoolProperty(
        name="Advanced simulation",
        description="Enables the advanced simulation, will take longer",
        default = False
    )
    

def unregister():
    bpy.utils.unregister_class(MY_UL_List)
    bpy.utils.unregister_class(LIST_OT_NewItem)
    bpy.utils.unregister_class(LIST_OT_DeleteItem)
    bpy.utils.unregister_class(LIST_OT_NewPreset)
    bpy.utils.unregister_class(PlanetProperties)
    bpy.utils.unregister_class(SolarSystem)
    bpy.utils.unregister_class(CreatePlanets)
    bpy.utils.unregister_class(SimulateSystem)
    
    del bpy.types.Scene.planet_enum
    del bpy.types.Scene.my_list 
    del bpy.types.Scene.list_index
    del bpy.types.Scene.sim_mode
    del bpy.types.Scene.scale_factor

if __name__ == "__main__":
    register()
