import math import os import sys import bpy def reset_scene(): bpy.ops.wm.read_factory_settings(use_empty=True) scene = bpy.context.scene scene.frame_start = 1 scene.frame_end = 24 scene.render.fps = 24 return scene def mesh_object(name, vertices, faces, location=(0.0, 0.0, 0.0)): mesh = bpy.data.meshes.new(f"{name}Mesh") mesh.from_pydata(vertices, [], faces) mesh.update() obj = bpy.data.objects.new(name, mesh) bpy.context.collection.objects.link(obj) obj.location = location return obj def cube_object(name, location=(0.0, 0.0, 0.0), scale=1.0): vertices = [ (-1, -1, -1), (1, -1, -1), (1, 1, -1), (-1, 1, -1), (-1, -1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1), ] faces = [ (0, 1, 2, 3), (4, 7, 6, 5), (0, 4, 5, 1), (1, 5, 6, 2), (2, 6, 7, 3), (4, 0, 3, 7), ] obj = mesh_object(name, vertices, faces, location) obj.scale = (scale, scale, scale) return obj def plane_grid(name, size=2.0, z=0.0, location=(0.0, 0.0, 0.0)): vertices = [(-size, -size, z), (0, -size, z), (size, -size, z), (-size, 0, z), (0, 0, z), (size, 0, z), (-size, size, z), (0, size, z), (size, size, z)] faces = [(0, 1, 4, 3), (1, 2, 5, 4), (3, 4, 7, 6), (4, 5, 8, 7)] return mesh_object(name, vertices, faces, location) def select_active(obj): bpy.ops.object.select_all(action="DESELECT") obj.select_set(True) bpy.context.view_layer.objects.active = obj def generate_fixture(output_path): reset_scene() x = -12.0 subsurf = cube_object("GenerateSubsurf", (x, 0, 0)) mod = subsurf.modifiers.new("Subdivision", "SUBSURF") mod.levels = 1 mod.render_levels = 1 x += 4 mirror = mesh_object("GenerateMirror", [(0.2, -1, -1), (1.8, -1, -1), (1.8, 1, -1), (0.2, 1, -1), (0.2, -1, 1), (1.8, -1, 1), (1.8, 1, 1), (0.2, 1, 1)], [(0, 1, 2, 3), (4, 7, 6, 5), (0, 4, 5, 1), (1, 5, 6, 2), (2, 6, 7, 3), (4, 0, 3, 7)], (x, 0, 0)) mod = mirror.modifiers.new("Mirror X", "MIRROR") mod.use_axis[0] = True mod.use_clip = True x += 4 array = cube_object("GenerateArray", (x, 0, 0), 0.6) mod = array.modifiers.new("Array Three", "ARRAY") mod.count = 3 mod.relative_offset_displace = (1.5, 0.0, 0.0) x += 4 bevel = cube_object("GenerateBevel", (x, 0, 0)) mod = bevel.modifiers.new("Bevel", "BEVEL") mod.width = 0.2 mod.segments = 2 x += 4 solidify = plane_grid("GenerateSolidify", 1.0, location=(x, 0, 0)) mod = solidify.modifiers.new("Solidify", "SOLIDIFY") mod.thickness = 0.25 x += 4 triangulate = cube_object("GenerateTriangulate", (x, 0, 0)) triangulate.modifiers.new("Triangulate", "TRIANGULATE") x += 4 weld = mesh_object("GenerateWeld", [(-1, -1, 0), (1, -1, 0), (1, 1, 0), (-1, -1, 0), (1, 1, 0), (-1, 1, 0)], [(0, 1, 2), (3, 4, 5)], (x, 0, 0)) mod = weld.modifiers.new("Weld", "WELD") mod.merge_threshold = 0.001 x += 4 boolean_source = cube_object("GenerateBoolean", (x, 0, 0)) boolean_target = cube_object("GenerateBooleanTarget", (x + 0.75, 0, 0), 0.75) boolean_target.display_type = "WIRE" mod = boolean_source.modifiers.new("Boolean Difference", "BOOLEAN") mod.operation = "DIFFERENCE" mod.solver = "EXACT" mod.object = boolean_target wireframe = cube_object("GenerateWireframe", (x + 4, 0, 0)) mod = wireframe.modifiers.new("Wireframe", "WIREFRAME") mod.thickness = 0.15 mod.use_replace = True mod.use_boundary = True bpy.context.scene.frame_set(1) bpy.ops.wm.save_as_mainfile(filepath=output_path, compress=True) def deform_fixture(output_path): reset_scene() lattice_data = bpy.data.lattices.new("DeformLatticeTargetData") lattice_data.points_u = 2 lattice_data.points_v = 2 lattice_data.points_w = 2 lattice_target = bpy.data.objects.new("DeformLatticeTarget", lattice_data) bpy.context.collection.objects.link(lattice_target) lattice_target.location = (-8, 0, 0) lattice_target.scale = (1.5, 1.5, 1.5) lattice_data.points[-1].co_deform.z += 0.75 lattice_source = cube_object("DeformLattice", (-8, 0, 0)) mod = lattice_source.modifiers.new("Lattice", "LATTICE") mod.object = lattice_target mod.strength = 0.8 hook_source = plane_grid("DeformHook", 1.0, location=(-3, 0, 0)) hook_target = bpy.data.objects.new("DeformHookTarget", None) bpy.context.collection.objects.link(hook_target) hook_target.location = (-3, 0, 1.25) mod = hook_source.modifiers.new("Hook", "HOOK") mod.object = hook_target mod.vertex_indices_set([4, 5, 7, 8]) mod.strength = 0.7 shrink_target = plane_grid("DeformShrinkwrapTarget", 1.5, z=0.0, location=(2, 0, 0)) shrink_source = plane_grid("DeformShrinkwrap", 1.0, z=0.8, location=(2, 0, 0)) shrink_source.data.vertices[4].co.z = 1.4 mod = shrink_source.modifiers.new("Shrinkwrap", "SHRINKWRAP") mod.target = shrink_target mod.wrap_method = "NEAREST_SURFACEPOINT" mod.offset = 0.1 simple = cube_object("DeformSimple", (7, 0, 0)) mod = simple.modifiers.new("Simple Twist", "SIMPLE_DEFORM") mod.deform_method = "TWIST" mod.deform_axis = "Z" mod.angle = math.radians(35.0) mesh_deform = cube_object("DeformMeshDeform", (12, 0, 0)) mesh_cage = cube_object("DeformMeshCage", (12, 0, 0), 1.4) for polygon in mesh_cage.data.polygons: polygon.flip() mesh_cage.data.update() mesh_cage.display_type = "WIRE" mod = mesh_deform.modifiers.new("Mesh Deform", "MESH_DEFORM") mod.object = mesh_cage select_active(mesh_deform) bpy.ops.object.meshdeform_bind(modifier=mod.name) if not mod.is_bound: raise RuntimeError("Mesh Deform fixture failed to bind") for vertex in mesh_cage.data.vertices: if vertex.co.z > 0.0: vertex.co.z += 0.75 mesh_cage.data.update() surface_deform = plane_grid("DeformSurface", 1.0, z=0.5, location=(17, 0, 0)) surface_target = plane_grid("DeformSurfaceTarget", 1.5, location=(17, 0, 0)) mod = surface_deform.modifiers.new("Surface Deform", "SURFACE_DEFORM") mod.target = surface_target select_active(surface_deform) bpy.ops.object.surfacedeform_bind(modifier=mod.name) if not mod.is_bound: raise RuntimeError("Surface Deform fixture failed to bind") surface_target.data.vertices[4].co.z += 0.75 surface_target.data.update() cast = cube_object("DeformCast", (22, 0, 0)) mod = cast.modifiers.new("Cast Sphere", "CAST") mod.cast_type = "SPHERE" mod.factor = 0.75 mod.use_radius_as_size = False mod.size = 1.25 smooth = plane_grid("DeformSmooth", 1.5, location=(27, 0, 0)) smooth.data.vertices[4].co.z = 1.5 mod = smooth.modifiers.new("Smooth", "SMOOTH") mod.factor = 0.5 mod.iterations = 2 bpy.context.scene.frame_set(1) bpy.ops.wm.save_as_mainfile(filepath=output_path, compress=True) def deform_curve_fixture(output_path): reset_scene() curve_data = bpy.data.curves.new("DeformCurveTargetData", "CURVE") curve_data.dimensions = "3D" spline = curve_data.splines.new("BEZIER") spline.bezier_points.add(2) for point, coordinate in zip(spline.bezier_points, [(-2, 0, 0), (0, 0.75, 0), (2, 0, 1)]): point.co = coordinate point.handle_left_type = "AUTO" point.handle_right_type = "AUTO" curve_target = bpy.data.objects.new("DeformCurveTarget", curve_data) bpy.context.collection.objects.link(curve_target) curve_source = plane_grid("DeformCurve", 1.5) modifier = curve_source.modifiers.new("Curve", "CURVE") modifier.object = curve_target modifier.deform_axis = "POS_X" bpy.context.scene.frame_set(1) bpy.ops.wm.save_as_mainfile(filepath=output_path, compress=True) def physics_fixture(output_path): scene = reset_scene() scene.frame_end = 12 build = cube_object("PhysicsBuild", (-4, 0, 0)) mod = build.modifiers.new("Build", "BUILD") mod.frame_start = 1 mod.frame_duration = 10 mod.use_random_order = False wave = plane_grid("PhysicsWave", 2.0, location=(2, 0, 0)) mod = wave.modifiers.new("Wave", "WAVE") mod.height = 0.4 mod.width = 1.2 mod.speed = 0.25 mod.start_position_x = 0.0 mod.start_position_y = 0.0 cloth = plane_grid("PhysicsClothDisabled", 1.0, location=(7, 0, 1)) mod = cloth.modifiers.new("Cloth Disabled", "CLOTH") mod.show_viewport = False soft = plane_grid("PhysicsSoftBodyDisabled", 1.0, location=(11, 0, 1)) select_active(soft) bpy.ops.object.modifier_add(type="SOFT_BODY") soft.modifiers[-1].name = "Soft Body Disabled" soft.modifiers[-1].show_viewport = False collision = cube_object("PhysicsCollisionDisabled", (15, 0, 0)) select_active(collision) bpy.ops.object.modifier_add(type="COLLISION") collision.modifiers[-1].name = "Collision Disabled" collision.modifiers[-1].show_viewport = False scene.frame_set(6) bpy.ops.wm.save_as_mainfile(filepath=output_path, compress=True) def geometry_nodes_fixture(output_path): reset_scene() obj = cube_object("GeometryNodesObject") group = bpy.data.node_groups.new("WebGeometryNodes", "GeometryNodeTree") group.interface.new_socket(name="Geometry", in_out="INPUT", socket_type="NodeSocketGeometry") group.interface.new_socket(name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry") input_node = group.nodes.new("NodeGroupInput") output_node = group.nodes.new("NodeGroupOutput") transform = group.nodes.new("GeometryNodeTransform") transform.inputs["Translation"].default_value = (0.25, 0.5, 1.0) transform.inputs["Rotation"].default_value = (0.0, 0.0, math.radians(15.0)) group.links.new(input_node.outputs["Geometry"], transform.inputs["Geometry"]) group.links.new(transform.outputs["Geometry"], output_node.inputs["Geometry"]) modifier = obj.modifiers.new("Geometry Nodes Transform", "NODES") modifier.node_group = group set_position_object = cube_object("GeometryNodesSetPosition", (4, 0, 0)) set_position_group = bpy.data.node_groups.new("WebGeometryNodesSetPosition", "GeometryNodeTree") set_position_group.interface.new_socket(name="Geometry", in_out="INPUT", socket_type="NodeSocketGeometry") set_position_group.interface.new_socket(name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry") set_position_input = set_position_group.nodes.new("NodeGroupInput") set_position_output = set_position_group.nodes.new("NodeGroupOutput") set_position = set_position_group.nodes.new("GeometryNodeSetPosition") set_position.inputs["Offset"].default_value = (0.0, 0.0, 1.0) set_position_group.links.new(set_position_input.outputs["Geometry"], set_position.inputs["Geometry"]) set_position_group.links.new(set_position.outputs["Geometry"], set_position_output.inputs["Geometry"]) modifier = set_position_object.modifiers.new("Geometry Nodes Set Position", "NODES") modifier.node_group = set_position_group simulation = cube_object("GeometryNodesSimulation", (8, 0, 0)) simulation_group = bpy.data.node_groups.new("WebGeometryNodesSimulation", "GeometryNodeTree") simulation_group.interface.new_socket(name="Geometry", in_out="INPUT", socket_type="NodeSocketGeometry") simulation_group.interface.new_socket(name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry") simulation_input = simulation_group.nodes.new("NodeGroupInput") simulation_output = simulation_group.nodes.new("NodeGroupOutput") zone_input = simulation_group.nodes.new("GeometryNodeSimulationInput") zone_output = simulation_group.nodes.new("GeometryNodeSimulationOutput") zone_input.pair_with_output(zone_output) simulation_group.links.new(simulation_input.outputs["Geometry"], zone_input.inputs["Geometry"]) simulation_group.links.new(zone_input.outputs["Geometry"], zone_output.inputs["Geometry"]) simulation_group.links.new(zone_output.outputs["Geometry"], simulation_output.inputs["Geometry"]) modifier = simulation.modifiers.new("Geometry Nodes Simulation", "NODES") modifier.node_group = simulation_group cube_object("GeometryNodesSimulationBaseline", (12, 0, 0)) bpy.context.scene.frame_set(1) bpy.ops.wm.save_as_mainfile(filepath=output_path, compress=True) def grease_pencil_fixture(output_path): reset_scene() grease_pencil = bpy.data.grease_pencils.new("GreasePencilData") layer = grease_pencil.layers.new("Lines", set_active=True) drawing = layer.frames.new(1).drawing drawing.add_strokes([4]) for point, coordinate in zip(drawing.strokes[0].points, [(-1.5, 0, 0), (-0.5, 0.5, 0), (0.5, -0.25, 0), (1.5, 0.25, 0)]): point.position = coordinate point.radius = 0.05 point.opacity = 0.9 obj = bpy.data.objects.new("GreasePencilObject", grease_pencil) bpy.context.collection.objects.link(obj) modifier_types = sorted(item.identifier for item in bpy.types.Modifier.bl_rna.properties["type"].enum_items if item.identifier.startswith("GREASE_PENCIL_") or item.identifier == "LINEART") for modifier_type in modifier_types: modifier = obj.modifiers.new(modifier_type.replace("GREASE_PENCIL_", "GP ").replace("_", " ").title(), modifier_type) modifier.show_viewport = False bpy.context.scene.frame_set(1) bpy.ops.wm.save_as_mainfile(filepath=output_path, compress=True) GENERATORS = { "generate": generate_fixture, "deform": deform_fixture, "deform_curve": deform_curve_fixture, "physics": physics_fixture, "geometry_nodes": geometry_nodes_fixture, "grease_pencil": grease_pencil_fixture, } def main(): if "--" not in sys.argv: raise SystemExit("usage: blender -b --python generate-modifier-fixtures.py -- output-directory") args = sys.argv[sys.argv.index("--") + 1:] if not args: raise SystemExit("missing output directory") output_directory = os.path.abspath(args[0]) os.makedirs(output_directory, exist_ok=True) requested = args[1:] or list(GENERATORS) for category in requested: if category not in GENERATORS: raise SystemExit(f"unknown fixture category: {category}") output_path = os.path.join(output_directory, f"modifier_{category}_scene.blend") GENERATORS[category](output_path) print(f"fixture-generated category={category} path={output_path}") if __name__ == "__main__": main()