def particle_system(
    emitter: bpy.types.Object,
    subject: Union[bpy.types.Object, bpy.types.Collection],
    settings: dict,
    collision_collection=None,
):
    """
    Generalized particle system.
    kwargs are passed through to particle_system.settings
    """

    if isinstance(subject, bpy.types.Collection):
        subject = as_particle_collection(subject)

    mod = emitter.modifiers.new(name="PARTICLE", type="PARTICLE_SYSTEM")
    system = mod.particle_system

    emitter.show_instancer_for_viewport = False
    emitter.show_instancer_for_render = False

    settings = copy(settings)

    if isinstance(subject, bpy.types.Object):
        system.settings.render_type = "OBJECT"
        system.settings.instance_object = subject
        objects = [subject]
    elif isinstance(subject, bpy.types.Collection):
        system.settings.render_type = "COLLECTION"
        system.settings.instance_collection = subject
        system.settings.use_collection_pick_random = True

        objects = list(subject.objects)
    else:
        raise ValueError(f"Unrecognized {type(subject)=}")

    butil.origin_set(objects, "ORIGIN_GEOMETRY", center="MEDIAN")

    dur = bpy.context.scene.frame_end - bpy.context.scene.frame_start
    system.settings.frame_start = bpy.context.scene.frame_start - settings.pop(
        "warmup_frames", 0
    )
    system.settings.frame_end = (
        bpy.context.scene.frame_start
        + settings.pop("emit_duration", dur)
        + settings.pop("warmup_frames", 0)
    )

    if (g := settings.pop("effect_gravity", None)) is not None:
        system.settings.effector_weights.gravity = g

    if (d := settings.pop("density", None)) is not None:
        assert "count" not in settings
        measure = math.prod([v for v in emitter.dimensions if v != 0])
        system.settings.count = math.ceil(d * measure)

    if (b := settings.pop("boids_settings", None)) is not None:
        system.settings.physics_type = "BOIDS"
        configure_boids(system.settings, b)

    if collision_collection is not None:
        system.settings.collision_collection = collision_collection

    for k, v in settings.items():
        setattr(system.settings, k, v)

    return emitter, system

    Generalized particle system.
    kwargs are passed through to particle_system.settings
    
infinigen/core/placement/particles.py