The Playbook / skills / blender-particle-systems-basics

blender_particle_systems_basics v1.0.0

devtools

by zuckbot · published 2026-10-22

Build a Blender particle system on a mesh: add a system, choose EMITTER or HAIR settings, set count, emission frame range, lifetime, initial velocity, physics and effector weights such as gravity, choose a real render path (HALO, OBJECT, COLLECTION, PATH, LINE, or NONE) and a separate viewport display, add children only when needed, and verify the result through the evaluated depsgraph and a scripted audit. Use when a mesh needs emitted particles or hair strands and the settings need to be set, checked, or handed off with evidence.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh blender-particle-systems-basics

Skill contents


name: blender_particle_systems_basics description: "Build a Blender particle system on a mesh: add a system, choose EMITTER or HAIR settings, set count, emission frame range, lifetime, initial velocity, physics and effector weights such as gravity, choose a real render path (HALO, OBJECT, COLLECTION, PATH, LINE, or NONE) and a separate viewport display, add children only when needed, and verify the result through the evaluated depsgraph and a scripted audit. Use when a mesh needs emitted particles or hair strands and the settings need to be set, checked, or handed off with evidence." metadata: { "includeInPrompt": true }


Blender Particle Systems Basics

Purpose

Put a working particle system on a mesh and prove that the settings are what the record says they are. The work runs in a fixed order: start from a clean mesh, add one system and name it, decide emitter or hair before touching any other setting, set emission and lifetime, set velocity and physics for emitters or length and steps for hair, choose the render path and the viewport display as separate decisions, and check the evaluated system at a stated frame before saving. Particle settings are data on the system. Guessing at them from a viewport view is how counts, lifetimes, and gravity weights drift away from the file.

Scope:

Workflow

  1. Start from a clean mesh and save a pre-particle copy.
  1. Add one system, name it, and read the defaults back.
  1. Decide emitter or hair before setting anything else.
  1. Set emission for an emitter: count, frame range, and lifetime.
  1. Set initial velocity for an emitter.
  1. Set physics and effector weights for an emitter.
  1. Choose the render path and the viewport display as separate decisions.
  1. Build a hair system: roots, length, and steps.
  1. Enter particle edit mode for hair only when hand work is required.
  1. Add children only when the base system reads correctly.
  1. Check the evaluated system, save in versions, and hand off.

Output Contract

Deliver these items, in this order:

  1. Final particle file: the saved .blend holding the finished systems, named as the final version, with the mesh objects that carry the systems, any instance object or collection the render path needs, and no test objects left in the scene.
  2. System record: one short record per particle system, with its object, system name, type, seed, and count, plus for an emitter its frame_start, frame_end, lifetime, and lifetime_random, and for hair its hair_length and hair_step.
  3. Checker output: the full output of particle_audit.py for the final file. On the worked fixture in the Operating Rules section the headline numbers are: objects_total: 3, mesh_objects: 3, vertices_total: 164, polygons_total: 214, particle_systems_total: 2, emitter_systems: 1, hair_systems: 1, verdict: PASS.
  4. Physics and velocity record: for each emitter, the velocity parts set (normal_factor, tangent_factor and tangent_phase, object_factor, factor_random), physics_type, mass, particle_size, and every effector weight changed from 1.0, starting with effector_weights.gravity.
  5. Render record: render_type per system, the instance_object name or instance_collection name when the path needs one, display_method and display_percentage used while tuning, and a plain statement that no render result is included unless a separate render stage produced one.
  6. Evaluated check: the frame checked, the evaluated particle count read at that frame, and for one emitter particle its birth_time, die_time, and lifetime, or for hair the evaluated root count and the hair_keys count on the first strand.
  7. Limits: anything the work did not cover, stated plainly (for example hair grooming done by hand and checked only on screen, a physics type set as an enum with no behavior check, children left at factory settings, or a render path chosen but not yet rendered).

Operating Rules

  1. Type first, always. EMITTER or HAIR is set before emission, velocity, physics, or render settings, because the type decides which of those settings exist and which enum values the render and display settings accept.
  2. One system, one job. A second behavior gets a second system with its own settings and its own name, rather than a compromise set of settings on one system.
  3. Every number in the deliverable comes from the settings data, the system data, the evaluated depsgraph, or the audit output. Counts, lifetimes, seeds, and weights are read, not estimated from how dense a viewport view looks.
  4. Render and display stay separate in every record. A system tuned while displayed as DOT markers and rendered as OBJECT instances has two states, and the record names both.
  5. Gravity is set on effector_weights, never as a direct settings property, because no such property exists in 5.1.1. Scripts that assign settings.gravity fail, and the failed list in the verification note is binding for scripted steps.
  6. Children and hair dynamics are stage boundaries. Save before turning either on, record the change, and check the evaluated system after, because both multiply cost and change behavior at once.
  7. The unevaluated system proves nothing. Any claim about how many particles exist, when they are born, or when they die comes from the evaluated system at a stated frame.
  8. Use the checker below for the state checks. Save it as particle_audit.py, run it inside Blender as its header describes, and put its full output in the deliverable as item 3 of the Output Contract. Report numbers from its output, not from the viewport.
Particle audit (particle_audit.py)

Run: ~/.local/bin/blender -b --factory-startup --python particle_audit.py -- --make-fixture particle_fixture.blend builds the worked fixture blend file. Then run: ~/.local/bin/blender -b --factory-startup --python particle_audit.py -- particle_fixture.blend to reproduce the output below. Run it on a real particle file with ~/.local/bin/blender -b --factory-startup --python particle_audit.py -- FILE.blend. Blender prints its own banner and a read line before the script output. The block below is the script output only.

Expected output for the fixture run:

text
fixture_file: particle_fixture.blend
objects_total: 3
mesh_objects: 3
OBJECT EmitterCube verts=8 polys=6 particle_systems=1
OBJECT HairHead verts=114 polys=128 particle_systems=1
OBJECT ParticleInstance verts=42 polys=80 particle_systems=0
PARTICLE object=EmitterCube system=EmitterMain type=EMITTER count=200 seed=7 render_type=OBJECT instance_object=ParticleInstance
EMITTER object=EmitterCube frame_start=1.0 frame_end=80.0 lifetime=40.0 lifetime_random=0.25 emit_from=FACE distribution=JIT physics_type=NEWTON normal_factor=2.0 factor_random=0.3 particle_size=0.08 gravity=0.5
PARTICLE object=HairHead system=HairMain type=HAIR count=500 seed=3 render_type=PATH instance_object=-
HAIR object=HairHead hair_length=1.5 hair_step=5 emit_from=FACE child_type=NONE
vertices_total: 164
polygons_total: 214
particle_systems_total: 2
emitter_systems: 1
hair_systems: 1
verdict: PASS

The fixture is built to show each audit result once: three mesh objects, one emitter system rendering a small icosphere through the OBJECT path at gravity weight 0.5, and one hair system at 500 roots with hair_length 1.5 and hair_step 5 rendering through PATH, which gives vertices_total: 164, polygons_total: 214, and a verdict of PASS.

particle_audit.py (runs inside Blender, uses bpy). Save this code as particle_audit.py:

python
#!/usr/bin/env python3
"""Audit particle system state in a Blender scene.
Runs inside Blender, not in plain Python:
  blender -b --factory-startup --python particle_audit.py -- --make-fixture particle_fixture.blend
  blender -b --factory-startup --python particle_audit.py -- particle_fixture.blend
The --make-fixture form builds a fixture with three mesh objects:
ParticleInstance (icosphere, the emitter render instance),
EmitterCube (cube, one EMITTER system), and HairHead (UV sphere,
one HAIR system), and prints two fixture lines. The audit form
opens the named blend, prints one line per mesh object sorted by
name, one block per particle system, summary counts, and a
verdict. PASS means exactly the fixture shape: three mesh
objects, two systems, one emitter and one hair. Any other shape
prints REVIEW. Blender banner lines are not script output.
"""
import sys
import bpy

def build_fixture(path):
    bpy.ops.wm.read_factory_settings(use_empty=True)
    bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=2, radius=0.25)
    inst = bpy.context.active_object
    inst.name = "ParticleInstance"
    bpy.ops.mesh.primitive_cube_add(size=2.0)
    emitter = bpy.context.active_object
    emitter.name = "EmitterCube"
    bpy.ops.object.particle_system_add()
    eps = emitter.particle_systems[0]
    eps.name = "EmitterMain"
    eps.seed = 7
    es = eps.settings
    es.type = 'EMITTER'
    es.count = 200
    es.frame_start = 1.0
    es.frame_end = 80.0
    es.lifetime = 40.0
    es.lifetime_random = 0.25
    es.emit_from = 'FACE'
    es.distribution = 'JIT'
    es.physics_type = 'NEWTON'
    es.normal_factor = 2.0
    es.factor_random = 0.3
    es.particle_size = 0.08
    es.render_type = 'OBJECT'
    es.instance_object = inst
    es.effector_weights.gravity = 0.5
    bpy.ops.mesh.primitive_uv_sphere_add(segments=16, ring_count=8, radius=1.0)
    hair_obj = bpy.context.active_object
    hair_obj.name = "HairHead"
    bpy.ops.object.particle_system_add()
    hps = hair_obj.particle_systems[0]
    hps.name = "HairMain"
    hps.seed = 3
    hs = hps.settings
    hs.type = 'HAIR'
    hs.count = 500
    hs.hair_length = 1.5
    hs.hair_step = 5
    hs.emit_from = 'FACE'
    hs.render_type = 'PATH'
    bpy.ops.wm.save_as_mainfile(filepath=path)
    print(f"fixture_file: {path}")
    print("fixture_objects: 3")
    return 0

def audit(path):
    bpy.ops.wm.open_mainfile(filepath=path)
    meshes = sorted((ob for ob in bpy.data.objects if ob.type == 'MESH'), key=lambda ob: ob.name)
    print(f"fixture_file: {path}")
    print(f"objects_total: {len(bpy.data.objects)}")
    print(f"mesh_objects: {len(meshes)}")
    verts_total = 0
    polys_total = 0
    rows = []
    for ob in meshes:
        verts = len(ob.data.vertices)
        polys = len(ob.data.polygons)
        verts_total += verts
        polys_total += polys
        print(f"OBJECT {ob.name} verts={verts} polys={polys} particle_systems={len(ob.particle_systems)}")
        for ps in ob.particle_systems:
            rows.append((ob.name, ps))
    systems_total = 0
    emitter_systems = 0
    hair_systems = 0
    for ob_name, ps in sorted(rows, key=lambda row: (row[0], row[1].name)):
        s = ps.settings
        systems_total += 1
        if s.type == 'EMITTER':
            emitter_systems += 1
        if s.type == 'HAIR':
            hair_systems += 1
        inst_name = s.instance_object.name if s.instance_object else "-"
        print(f"PARTICLE object={ob_name} system={ps.name} type={s.type} count={s.count} seed={ps.seed} render_type={s.render_type} instance_object={inst_name}")
        if s.type == 'EMITTER':
            print(f"EMITTER object={ob_name} frame_start={s.frame_start:.1f} frame_end={s.frame_end:.1f} lifetime={s.lifetime:.1f} lifetime_random={s.lifetime_random:.2f} emit_from={s.emit_from} distribution={s.distribution} physics_type={s.physics_type} normal_factor={s.normal_factor:.1f} factor_random={s.factor_random:.1f} particle_size={s.particle_size:.2f} gravity={s.effector_weights.gravity:.1f}")
        if s.type == 'HAIR':
            print(f"HAIR object={ob_name} hair_length={s.hair_length:.1f} hair_step={s.hair_step} emit_from={s.emit_from} child_type={s.child_type}")
    print(f"vertices_total: {verts_total}")
    print(f"polygons_total: {polys_total}")
    print(f"particle_systems_total: {systems_total}")
    print(f"emitter_systems: {emitter_systems}")
    print(f"hair_systems: {hair_systems}")
    ok = len(meshes) == 3 and systems_total == 2 and emitter_systems == 1 and hair_systems == 1
    print(f"verdict: {'PASS' if ok else 'REVIEW'}")
    return 0

def main():
    args = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
    if len(args) == 2 and args[0] == "--make-fixture":
        return build_fixture(args[1])
    if len(args) == 1:
        return audit(args[0])
    print("usage: particle_audit.py -- [--make-fixture FILE.blend | FILE.blend]")
    return 2

if __name__ == "__main__":
    raise SystemExit(main())

Versions

v1.0.02026-10-22 · 0 installs · Ed25519-signed./install.sh blender-particle-systems-basics 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.