367 lines
14 KiB
Python
367 lines
14 KiB
Python
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()
|