Hi!
I’m new to Python and somewhat intermediate in coding in general and I’ve been using Blender for years, on and off.
I’ve been trying to create an addon that would help me with creating and modifying solar systems for worldbuilding and art needs. At first, I tried to do it with geometry nodes and was somewhat successful, but had to do multiple things manually each time I added a new planet or a moon (mainly setting up and copying drivers). That system also moved the geometry of objects, but didn’t allow me to move the objects themselves with their origins, as far as I know - I really tried.
These were great limitations and I decided to tackle the problem with scripting. But I didn’t get very far before I encountered a big problem - any time I change a property, I have to manually press the update button to see the change in the orbit of the body. I need to see the visual change right away when changing the value with the slider in the UI…it is also needed for animating the whole thing.
In the Blender Python API, it says here that you can call a method any time you update the property, once initialized. But it just does not work. I’ve tried different methods, different properties, but nothing whatsoever is called when the change happens. I’m quite confused in general with this API scripting business, but this takes the cake. I couldn’t find an answer online (only supposedly working examples…), so I’m writing here. I’m probably missing something obvious.
Anyway, here’s my entire code (there’s problems, but I just really want to know how to do the update), I’ll appreciate any pointers. Thanks!
bl_info = {
"name" : "Solar System Creator",
"author" : "Ondrej Hrdina",
"version" : (1, 0),
"blender" : (3, 40, 0),
"location" : "View3d > Tool",
"warning" : "hi",
"wiki_url" : "",
"category" : "Add Mesh",
}
import bpy
import math
#list of all celestial bodies in the system
Bodies = []
#updates position of all bodies
def update_coordinates(self, context):
time = bpy.context.scene.time
for b in Bodies:
e = b["eccentricity"]
period = b["period"]
major = b["semi_major_axis"]
minor = OrbitalMechanics.minor(e, major, period)
xy = OrbitalMechanics.xy(e,OrbitalMechanics.e_anomaly(e, period, time, 3), major, minor)
b.location[0] = xy[0]
b.location[1] = xy[1]
#button to call the update_coordinates method
class Update(bpy.types.Operator):
bl_idname = "system.update"
bl_label = "Update Coordinates"
def execute(self, context):
update_coordinates(self, context)
return {'FINISHED'}
#button to add a new body to the list
class AddBodyDialogOperator(bpy.types.Operator):
"""Add a celestial empty"""
bl_idname = "system.add_body_dialog"
bl_label = "Add Body"
#prompt properties from user
name : bpy.props.StringProperty(name= "Enter name", default= "Arda")
mass : bpy.props.FloatProperty(name= "Enter mass in Earth masses", default= 1, min= 0)
semi_major_axis : bpy.props.FloatProperty(name ="Enter semi-major axis from parent in AU", default= 1, min= 0)
eccentricity : bpy.props.FloatProperty(name ="Enter eccentricity from parent in AU", default= 0, min= 0, max= 1)
period : bpy.props.FloatProperty(name ="Period in Earth years", default= 1, min= 0)
def execute(self, context):
#add an empty cube
bpy.ops.object.empty_add(type='SPHERE', align='WORLD')
body = bpy.context.object
#assign properties
body.name = self.name
body['mass'] = self.mass
body['semi_major_axis'] = self.semi_major_axis
body['eccentricity'] = self.eccentricity
body['period'] = self.period
#add the body to the list
Bodies.append(body)
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
class OrbitalMechanics():
#method to calculate the x and y coordinates from the eccentric anomaly and the major and minor semi axes
def xy(e, e_anomaly, major, minor):
x = (math.cos(e_anomaly) - e)*major
y = math.sin(e_anomaly)*minor
return [x,y]
#method to iteratively approximate eccentric anomaly from eccentricity and period
def e_anomaly(e, period, time, iterations):
time = bpy.context.scene.time
mean_motion = 2*math.pi/period
mean_anomaly = time*mean_motion
e_anomaly = mean_anomaly
i = 0
while i < iterations:
e_anomaly = e_anomaly - (((e_anomaly - math.sin(e_anomaly)*e) - mean_anomaly)/(1 - math.cos(e_anomaly)*e))
i += 1
return e_anomaly
#method to calculate the semi-minor axis from the semi-major axis and eccentricity
def minor(e, major, period):
minor = major*math.sqrt(1 - math.pow(e,2))
return minor
class MainPanel(bpy.types.Panel):
bl_label = "Solar System"
bl_idname = "MainPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Solar System'
def draw(self, context):
layout = self.layout
layout.operator(AddBodyDialogOperator.bl_idname)
layout.operator(Update.bl_idname)
row = layout.row()
layout.prop(bpy.context.scene, '["time"]')
#panel to show current body's properties
class BodyDetailPanel(bpy.types.Panel):
bl_label = "Body Detail"
bl_idname = "BodyDetailPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Solar System'
bl_parent_id = 'MainPanel'
def draw(self, context):
if bpy.context.object != None:
layout = self.layout
layout.prop(bpy.context.object, '["mass"]')
layout.prop(bpy.context.object, '["semi_major_axis"]')
layout.prop(bpy.context.object, '["eccentricity"]',slider= True)
bpy.context.object.id_properties_ui("eccentricity").update(min=0,max=1)
layout.prop(bpy.context.object, '["period"]')
#scene time to direct all motion
bpy.types.Scene.time = bpy.props.FloatProperty(default= 0, update= update_coordinates)
time = bpy.context.scene.time
bpy.context.scene.time = bpy.context.scene.time
classes = (Update, AddBodyDialogOperator, MainPanel, BodyDetailPanel)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
if __name__== "__main__":
register()