# How to add BOID brain settings to a particle system via python script?

I am trying to use a script to add some rules to the boid brain of a particle system and the operators on website here, https://docs.blender.org/api/current/bpy.ops.boid.html, don't seem to be doing anything. Does anyone know a way to do add various rules to a boid brain system via script?

I am using the following code to add the particle system to an existing selected object in the scene. Everything works up until the last line where I try to add the Boid rule. That line doesn't seem to do anything to the particle system.

#adding particle system with custom particle system settings for smoke explosion below

obj = bpy.context.active_object

if len(obj.particle_systems) == 0:
obj.modifiers.new("part", type='PARTICLE_SYSTEM')
part = obj.particle_systems[0]

settings = part.settings
settings.name = 'Bug System'
settings.count = 1000
settings.frame_start = 1
settings.frame_end = (250)
settings.emit_from = 'FACE'
settings.physics_type = 'BOIDS'
bpy.context.object.show_instancer_for_render = False
settings.show_unborn = True
settings.render_type = 'COLLECTION'
settings.particle_size = 0.05
settings.size_random = 0.3
bpy.data.particles["Bug System"].instance_collection = bpy.data.collections[context.scene.spyderfy_tool.my_stringbugsystem]
settings.show_unborn = False
settings.jitter_factor = 0
settings.object_factor = 1
settings.effector_weights.turbulence = 0
#adding boid brain rules in correct order

return{'FINISHED'}


UPDATE EDIT and Follow Up Question: lemons answer worked for adding the boid rules. The last issue I am having now is adding a goal target via the script as well? Do I need to override something here as well. I've attached the code below. Only the last line is not working. For clarification the "Bug System Goal" object is an Empty that has already been added to the scene.

#adding BOID BRAIN RULES in correct order(help from stackexchange users lemon and batfinger)
# Copy the current context
context_override = bpy.context.copy()
# Override the wanted property
context_override["particle_settings"] = settings
# Call the operators
#conditional to follow bug system goal if checkbox is selected by user
if (context.scene.spyderfy_tool.my_goalobject == True):
bpy.data.particles["Bug System"].rules["Goal"].object = bpy.data.objects["Bug System Goal"]


SECOND UPDATE and troubleshooting: Hi Lemon thanks for the response. Using the code you mentioned to add a goal object in my script I get the following error saying that the particle system object has no attribute "settings".

Tried to fill in the gaps in my code that would be specific to my script. I must be overlooking something? The goal assignment code where the error occurs is written last under the conditional statement.

 #Now we adjust our newly appended particle system with custom particle system settings for the boids system below(from article on stackexchange)

obj = bpy.context.active_object

if len(obj.particle_systems) == 0:
obj.modifiers.new("part", type='PARTICLE_SYSTEM')
part = obj.particle_systems[0]

settings = part.settings
settings.name = 'Bug System'
settings.count = 1000
settings.frame_start = 1
settings.frame_end = (250)
settings.emit_from = 'FACE'
settings.physics_type = 'BOIDS'
bpy.context.object.show_instancer_for_render = False
settings.show_unborn = True
settings.render_type = 'COLLECTION'
settings.particle_size = 0.05
settings.size_random = 0.3
bpy.data.particles["Bug System"].instance_collection = bpy.data.collections[context.scene.spyderfy_tool.my_stringbugsystem]
settings.show_unborn = False
settings.jitter_factor = 0
settings.object_factor = 1
settings.effector_weights.turbulence = 0
#adding BOID BRAIN RULES in correct order(help from stack exchange users lemon and batfinger)
# Copy the current context
context_override = bpy.context.copy()
# Override the wanted property
context_override["particle_settings"] = settings
# Call the operators
bpy.ops.boid.rule_del(context_override)
bpy.ops.boid.rule_del(context_override)

if (context.scene.spyderfy_tool.my_goalobject == True):
# Get the particle object
obj = bpy.context.object
# Get the goal
empty = bpy.data.objects["Bug System Goal"]

particle_system = bpy.data.particles["Bug System"]
# You can also:
#particle_system = obj.particle_systems["Bug System"]

# Get the boid part
boids_part = particle_system.settings.boids #WHERE THE ERROR OCCURS

# Get the state (it seems there is one boid state)
state = boids_part.states[0]

# Get the 'Goal' rule from its index
# You should know its index as you've set it up earlier
rule = state.rules[1] # '1' is an example, so
# Or:
#rule = state.rules['Goal'] # But may be the same name several times

# Assign the empty as goal
rule.object = empty


Not sure where I am going wrong here.

You need to override the context. Context properties are described here. And more specifically, for particle settings, here.

To make it work, just add the following to your code:

# Copy the current context
context_override = bpy.context.copy()
# Override the wanted property
context_override["particle_settings"] = settings
# Call the operator


To assign an object as goal, you can:

import bpy

# Get the particle object
obj = bpy.context.object
# Get the goal
empty = bpy.data.objects["Empty"]

settings = bpy.data.particles["ParticleSettings"]
# You can also:
# settings = obj.particles_systems["ParticleSettings"].settings

# Get the boid part
boids_part = settings.boids

# Get the state (it seems there is one boid state)
state = boids_part.states[0]
# Or:
#state = boids_part.states['State']

# Get the 'Goal' rule from its index
# You should know its index as you've set it up earlier
rule = state.rules[2] # '2' is an example, so
# Or:
#rule = state.rules['Goal'] # But may be the same name several times

# Assign the empty as goal
rule.object = empty

• Thank you lemon. Works great! Much appreciated! – Brad Hamilton Dec 30 '20 at 19:42
• Hi lemon, I just had a follow up question regarding adding a goal object to the goal brain settings. Attached the updated script above in my initial question. – Brad Hamilton Jan 1 at 22:16
• Hi Lemon, Thanks for the update. Seem to still get an error on the line on "getting the boid part" Must be missing something? I attached a second update with code and details above. – Brad Hamilton Jan 2 at 22:09
• My bad. The code is corrected above. – lemon Jan 3 at 8:47
• Hey Lemon, thanks for looking again! Somehow I just figured out a way to add the goal to the system but I'm not actually sure why what I did worked. I'll take a look at your update too. Been trying to get my head around Batfinger's answer all night. Still have lots of python to learn and really appreciate both you and Batfingers help! -Brad – Brad Hamilton Jan 3 at 9:06

Some further notes on this.

AFAIC @lemon has provided the answer re which member is required to override. This is a little TL;DR ...looked at this prior to answer having tried "particle_system"., didn't work, put in the look at later.

The properties panel has what is known as 'BUTTONS' context which contains particle_system, particle_system_editable and particle_settings See https://docs.blender.org/api/current/bpy.context.html#bpy.context.particle_system

This is where the particle settings are displayed. If we view source in the particle tab, opening UI file bl_ui/poperties_particle.py can see that a little helper function is included to get the particle settings from context. Notice it uses either the context member available or those pinned to the panel.

def particle_get_settings(context):
if context.particle_system:
return context.particle_system.settings
elif isinstance(context.space_data.pin_id, bpy.types.ParticleSettings):
return context.space_data.pin_id
return None


One wonders why it doesn't use context.particle_settings. Since you are adding a new system, and quite likely not from the properties space will not use this.. moving on.

Other things to look for are the setting of a context pointer in a draw method, eg in this case something like

def draw(self, context):
self.layout.context_pointer_set(
"particle_settings",
particle_get_settings(context)
)
self.layout.operator("boid.foo")


Not used here but another way to set a context member. THe operator "boid.foo" would use the context.particle_settings a returned from the method Not used in this case moving on...

Using the reference

In as much as you have checked for no particle systems before adding one, and referencing it as the zeroth time of the objects particle systems... Can instead get the particle system from the new modifiers reference.

>>> mod = C.object.modifiers.new("Foo", type='PARTICLE_SYSTEM')
>>> ps = mod.particle_system
>>> settings = ps.settings
>>> mod
bpy.data.objects['Cube'].modifiers["Foo"]

>>> ps
bpy.data.objects['Cube'].particle_systems["Foo"]

>>> settings
bpy.data.particles['Foo']


Setting to boids, adds two default rules separate and flock, in that order.

>>> settings.physics_type = 'BOIDS'
>>> settings.boids.states['State'].rules[:]
[bpy.data.particles['Foo'].rules["Separate"], bpy.data.particles['Foo'].rules["Flock"]]


Note that using settings.boids.active_boid_state.rule avoids the need for state name.

Finding the override.

A usual rule of thumb to find an override, armed with the big possibility that outside of properties area need to set one of the aforementioned particlish context members, can check using the operators poll method.

>>> override = {
...     "particle_system" : ps,
...     "particle_editable_system" : ps,
...     "particle_settings" : settings,
...     }

True

{'CANCELLED'}


Which is a bummer, if there was a poll error could try polling against different context members... but alas in this instance the boid operators poll regardless.

>>> bpy.ops.boid.rule_add.poll(override)
True

>>> bpy.ops.boid.rule_add.poll({"snoo" : 'LUMP'})
True


Sheesh, not much luck, moving on

>>> bpy.ops.boid.rule_move_up(override)
{'FINISHED'}


At last, the Eureka moment of seeing 'FINISHED' indicating the operator has done its thing.

Cleaning the slate.

Ok, using the single context override as suggested by @lemon

May be simpler to remove the two default rules and rebuild rather than a list shuffle.

>>> while settings.boids.active_boid_state.rules:
...     bpy.ops.boid.rule_del({"particle_settings" : settings})
...
{'FINISHED'}
{'FINISHED'}


Note, am of the opinion since 2.8 can pass only the overridden props to the operator.

Add the new rules in order.

Example. Add the new rules based on some data. If the rule is a goal, set the goal object to some previously set target object.

>>> state = settings.boids.active_boid_state
>>> rules = ('FLOCK', 'GOAL', 'FIGHT', 'FLOCK')
>>> for rule in rules:
...     bpy.ops.boid.rule_add({"particle_settings" : settings}, type=rule)
...     if rule == 'GOAL':
...         state.active_boid_rule.object = target_object
...
{'FINISHED'}
{'FINISHED'}
{'FINISHED'}
{'FINISHED'}

• Thanks for the clarification batFINGER! The last bit you added aboout adding the rules in order is really helpful. The other parts as well but still wrapping my head around the concepts. – Brad Hamilton Dec 30 '20 at 19:44