Hi
I want convert obj sequence exist in a folder to alembic file using Python.
Is that possible?
It has to be python ? OR just interested in a solution… ?? Because… geometry nodes… ( you know ??)
Thanks , yes i need python code
Too bad… just saw this the other day
Theres some info here that maybe of use…
Hope that helps
You can use the geo nodes setup mentioned by @Okidoki above and then use default alembic exporter bpy.ops.wm.alembic_export()
to export the alembic.
If you want, you can fully automate it by importing objs and creating this geonodes setup from Python code.
Saved geo nodes setup to Python code with https://extensions.blender.org/add-ons/node-to-python/:
import bpy, mathutils
#initialize obj_sequence_player node group
def obj_sequence_player_node_group():
obj_sequence_player = bpy.data.node_groups.new(type = 'GeometryNodeTree', name = "Obj Sequence Player")
obj_sequence_player.color_tag = 'NONE'
obj_sequence_player.description = ""
obj_sequence_player.default_group_node_width = 140
obj_sequence_player.is_modifier = True
#obj_sequence_player interface
#Socket Geometry
geometry_socket = obj_sequence_player.interface.new_socket(name = "Geometry", in_out='OUTPUT', socket_type = 'NodeSocketGeometry')
geometry_socket.attribute_domain = 'POINT'
#Socket Geometry
geometry_socket_1 = obj_sequence_player.interface.new_socket(name = "Geometry", in_out='INPUT', socket_type = 'NodeSocketGeometry')
geometry_socket_1.attribute_domain = 'POINT'
#Socket Obj Sequene Collection
obj_sequene_collection_socket = obj_sequence_player.interface.new_socket(name = "Obj Sequene Collection", in_out='INPUT', socket_type = 'NodeSocketCollection')
obj_sequene_collection_socket.attribute_domain = 'POINT'
#Socket Starting Frame
starting_frame_socket = obj_sequence_player.interface.new_socket(name = "Starting Frame", in_out='INPUT', socket_type = 'NodeSocketFloat')
starting_frame_socket.default_value = 0.0
starting_frame_socket.min_value = -3.4028234663852886e+38
starting_frame_socket.max_value = 3.4028234663852886e+38
starting_frame_socket.subtype = 'NONE'
starting_frame_socket.attribute_domain = 'POINT'
#initialize obj_sequence_player nodes
#node Group Input
group_input = obj_sequence_player.nodes.new("NodeGroupInput")
group_input.name = "Group Input"
#node Group Output
group_output = obj_sequence_player.nodes.new("NodeGroupOutput")
group_output.name = "Group Output"
group_output.is_active_output = True
#node Mesh Line
mesh_line = obj_sequence_player.nodes.new("GeometryNodeMeshLine")
mesh_line.name = "Mesh Line"
mesh_line.count_mode = 'TOTAL'
mesh_line.mode = 'OFFSET'
#Count
mesh_line.inputs[0].default_value = 1
#Start Location
mesh_line.inputs[2].default_value = (0.0, 0.0, 0.0)
#Offset
mesh_line.inputs[3].default_value = (0.0, 0.0, 1.0)
#node Mesh to Points
mesh_to_points = obj_sequence_player.nodes.new("GeometryNodeMeshToPoints")
mesh_to_points.name = "Mesh to Points"
mesh_to_points.mode = 'VERTICES'
#Selection
mesh_to_points.inputs[1].default_value = True
#Position
mesh_to_points.inputs[2].default_value = (0.0, 0.0, 0.0)
#Radius
mesh_to_points.inputs[3].default_value = 0.05000000074505806
#node Instance on Points
instance_on_points = obj_sequence_player.nodes.new("GeometryNodeInstanceOnPoints")
instance_on_points.name = "Instance on Points"
#Selection
instance_on_points.inputs[1].default_value = True
#Pick Instance
instance_on_points.inputs[3].default_value = True
#Rotation
instance_on_points.inputs[5].default_value = (0.0, 0.0, 0.0)
#Scale
instance_on_points.inputs[6].default_value = (1.0, 1.0, 1.0)
#node Collection Info
collection_info = obj_sequence_player.nodes.new("GeometryNodeCollectionInfo")
collection_info.name = "Collection Info"
collection_info.transform_space = 'ORIGINAL'
#Separate Children
collection_info.inputs[1].default_value = True
#Reset Children
collection_info.inputs[2].default_value = False
#node Scene Time
scene_time = obj_sequence_player.nodes.new("GeometryNodeInputSceneTime")
scene_time.name = "Scene Time"
#node Math
math = obj_sequence_player.nodes.new("ShaderNodeMath")
math.name = "Math"
math.operation = 'SUBTRACT'
math.use_clamp = False
#node Realize Instances
realize_instances = obj_sequence_player.nodes.new("GeometryNodeRealizeInstances")
realize_instances.name = "Realize Instances"
#Selection
realize_instances.inputs[1].default_value = True
#Realize All
realize_instances.inputs[2].default_value = True
#Depth
realize_instances.inputs[3].default_value = 0
#Set locations
group_input.location = (-452.95648193359375, -143.3688201904297)
group_output.location = (674.0150756835938, 41.04326629638672)
mesh_line.location = (-112.06446838378906, 99.2362289428711)
mesh_to_points.location = (87.45671844482422, 83.96038818359375)
instance_on_points.location = (314.2950744628906, 61.222389221191406)
collection_info.location = (-64.86137390136719, -267.17193603515625)
scene_time.location = (-380.25958251953125, -514.1887817382812)
math.location = (-99.42635345458984, -497.7755432128906)
realize_instances.location = (494.0000305175781, 77.88040161132812)
#Set dimensions
group_input.width, group_input.height = 199.26803588867188, 100.0
group_output.width, group_output.height = 140.0, 100.0
mesh_line.width, mesh_line.height = 140.0, 100.0
mesh_to_points.width, mesh_to_points.height = 140.0, 100.0
instance_on_points.width, instance_on_points.height = 140.0, 100.0
collection_info.width, collection_info.height = 140.0, 100.0
scene_time.width, scene_time.height = 140.0, 100.0
math.width, math.height = 140.0, 100.0
realize_instances.width, realize_instances.height = 140.0, 100.0
#initialize obj_sequence_player links
#realize_instances.Geometry -> group_output.Geometry
obj_sequence_player.links.new(realize_instances.outputs[0], group_output.inputs[0])
#mesh_line.Mesh -> mesh_to_points.Mesh
obj_sequence_player.links.new(mesh_line.outputs[0], mesh_to_points.inputs[0])
#mesh_to_points.Points -> instance_on_points.Points
obj_sequence_player.links.new(mesh_to_points.outputs[0], instance_on_points.inputs[0])
#collection_info.Instances -> instance_on_points.Instance
obj_sequence_player.links.new(collection_info.outputs[0], instance_on_points.inputs[2])
#group_input.Obj Sequene Collection -> collection_info.Collection
obj_sequence_player.links.new(group_input.outputs[1], collection_info.inputs[0])
#scene_time.Frame -> math.Value
obj_sequence_player.links.new(scene_time.outputs[1], math.inputs[0])
#math.Value -> instance_on_points.Instance Index
obj_sequence_player.links.new(math.outputs[0], instance_on_points.inputs[4])
#group_input.Starting Frame -> math.Value
obj_sequence_player.links.new(group_input.outputs[2], math.inputs[1])
#instance_on_points.Instances -> realize_instances.Geometry
obj_sequence_player.links.new(instance_on_points.outputs[0], realize_instances.inputs[0])
return obj_sequence_player
obj_sequence_player = obj_sequence_player_node_group()
Ohh… maybe the main/real question is: How to convert several numbered objects into an animation? (After this any export with an animation aware format should be the lesser problem.)
Other alternative, besides geo nodes setup, is to import objs as a shape key animation.
Thanks @Andrej
I mix different scripts with help of chatgpt and claude.ai
To create this script obj_sequence_to_abc.py
and run it from cmd
import bpy
import os
import sys
import mathutils
def import_obj_sequence(directory_path):
# Get sorted list of OBJ files
obj_files = sorted([f for f in os.listdir(directory_path) if f.endswith('.obj')])
if not obj_files:
print(f"No OBJ files found in {directory_path}")
return None, None
# Remove existing collections if they exist
for col_name in ['firstframe', 'animation']:
if col_name in bpy.data.collections:
collection = bpy.data.collections[col_name]
for scn in bpy.data.scenes:
if collection in scn.collection.children:
scn.collection.children.unlink(collection)
bpy.data.collections.remove(collection)
# Create new collections
firstframe_collection = bpy.data.collections.new('firstframe')
animation_collection = bpy.data.collections.new('animation')
# Link collections to the scene
bpy.context.scene.collection.children.link(firstframe_collection)
bpy.context.scene.collection.children.link(animation_collection)
# Hide the animation collection in the viewport
animation_collection.hide_viewport = True
# Import first frame
first_obj_path = os.path.join(directory_path, obj_files[0])
try:
print(f"Importing first frame: {first_obj_path}")
# Use low-level mesh creation
mesh = bpy.data.meshes.new(name="FirstFrameMesh")
with open(first_obj_path, 'r') as f:
vertices = []
faces = []
for line in f:
if line.startswith('v '):
# Parse vertex coordinates
_, x, y, z = line.split()
vertices.append((float(x), float(y), float(z)))
elif line.startswith('f '):
# Parse face indices (subtract 1 for 0-based indexing)
face = [int(v.split('/')[0])-1 for v in line.split()[1:]]
faces.append(face)
# Create mesh from vertices and faces
mesh.from_pydata(vertices, [], faces)
mesh.update()
# Create object and link to scene and first frame collection
obj = bpy.data.objects.new("FirstFrameObject", mesh)
firstframe_collection.objects.link(obj)
bpy.context.scene.collection.objects.link(obj)
except Exception as e:
print(f"Error importing first frame: {e}")
return None, None
# Import remaining frames similarly
for obj_file in obj_files[0:]:
try:
obj_path = os.path.join(directory_path, obj_file)
print(f"Importing frame: {obj_path}")
# Create mesh
mesh = bpy.data.meshes.new(name=f"AnimationMesh_{obj_file}")
with open(obj_path, 'r') as f:
vertices = []
faces = []
for line in f:
if line.startswith('v '):
_, x, y, z = line.split()
vertices.append((float(x), float(y), float(z)))
elif line.startswith('f '):
face = [int(v.split('/')[0])-1 for v in line.split()[1:]]
faces.append(face)
# Create mesh from vertices and faces
mesh.from_pydata(vertices, [], faces)
mesh.update()
# Create object and link to animation collection
obj = bpy.data.objects.new(f"AnimationObject_{obj_file}", mesh)
animation_collection.objects.link(obj)
bpy.context.scene.collection.objects.link(obj)
except Exception as e:
print(f"Error importing frame {obj_file}: {e}")
print(f"Imported {len(obj_files)} OBJ files")
return firstframe_collection, animation_collection
def obj_sequence_player_node_group():
# Check if the node group already exists
if "Obj Sequence Player" in bpy.data.node_groups:
print("Node group 'Obj Sequence Player' already exists.")
return bpy.data.node_groups["Obj Sequence Player"]
obj_sequence_player = bpy.data.node_groups.new(type = 'GeometryNodeTree', name = "Obj Sequence Player")
obj_sequence_player.color_tag = 'NONE'
obj_sequence_player.description = ""
obj_sequence_player.default_group_node_width = 140
obj_sequence_player.is_modifier = True
#obj_sequence_player interface
#Socket Geometry
geometry_socket = obj_sequence_player.interface.new_socket(name = "Geometry", in_out='OUTPUT', socket_type = 'NodeSocketGeometry')
geometry_socket.attribute_domain = 'POINT'
#Socket Geometry
geometry_socket_1 = obj_sequence_player.interface.new_socket(name = "Geometry", in_out='INPUT', socket_type = 'NodeSocketGeometry')
geometry_socket_1.attribute_domain = 'POINT'
#initialize obj_sequence_player nodes
#node Group Output
group_output = obj_sequence_player.nodes.new("NodeGroupOutput")
group_output.name = "Group Output"
group_output.is_active_output = True
#node Mesh Line
mesh_line = obj_sequence_player.nodes.new("GeometryNodeMeshLine")
mesh_line.name = "Mesh Line"
mesh_line.count_mode = 'TOTAL'
mesh_line.mode = 'OFFSET'
#Count
mesh_line.inputs[0].default_value = 1
#Start Location
mesh_line.inputs[2].default_value = (0.0, 0.0, 0.0)
#Offset
mesh_line.inputs[3].default_value = (0.0, 0.0, 1.0)
#node Mesh to Points
mesh_to_points = obj_sequence_player.nodes.new("GeometryNodeMeshToPoints")
mesh_to_points.name = "Mesh to Points"
mesh_to_points.mode = 'VERTICES'
#Selection
mesh_to_points.inputs[1].default_value = True
#Position
mesh_to_points.inputs[2].default_value = (0.0, 0.0, 0.0)
#Radius
mesh_to_points.inputs[3].default_value = 0.05000000074505806
#node Instance on Points
instance_on_points = obj_sequence_player.nodes.new("GeometryNodeInstanceOnPoints")
instance_on_points.name = "Instance on Points"
#Selection
instance_on_points.inputs[1].default_value = True
#Pick Instance
instance_on_points.inputs[3].default_value = True
#Rotation
instance_on_points.inputs[5].default_value = (0.0, 0.0, 0.0)
#Scale
instance_on_points.inputs[6].default_value = (1.0, 1.0, 1.0)
#node Collection Info
collection_info = obj_sequence_player.nodes.new("GeometryNodeCollectionInfo")
collection_info.name = "Collection Info"
collection_info.transform_space = 'ORIGINAL'
if "animation" in bpy.data.collections:
collection_info.inputs[0].default_value = bpy.data.collections["animation"]
#Separate Children
collection_info.inputs[1].default_value = True
#Reset Children
collection_info.inputs[2].default_value = False
#node Scene Time
scene_time = obj_sequence_player.nodes.new("GeometryNodeInputSceneTime")
scene_time.name = "Scene Time"
#node Math
math = obj_sequence_player.nodes.new("ShaderNodeMath")
math.name = "Math"
math.operation = 'SUBTRACT'
math.use_clamp = False
#Value_001
math.inputs[1].default_value = 1.0
#node Realize Instances
realize_instances = obj_sequence_player.nodes.new("GeometryNodeRealizeInstances")
realize_instances.name = "Realize Instances"
#Selection
realize_instances.inputs[1].default_value = True
#Realize All
realize_instances.inputs[2].default_value = True
#Depth
realize_instances.inputs[3].default_value = 0
#Set locations
group_output.location = (674.0150756835938, 41.04326629638672)
mesh_line.location = (-112.06446838378906, 99.2362289428711)
mesh_to_points.location = (87.45671844482422, 83.96038818359375)
instance_on_points.location = (314.2950744628906, 61.222389221191406)
collection_info.location = (-64.86137390136719, -267.17193603515625)
scene_time.location = (-332.7658386230469, -545.5699462890625)
math.location = (-99.42635345458984, -497.7755432128906)
realize_instances.location = (494.0000305175781, 77.88040161132812)
#Set dimensions
group_output.width, group_output.height = 140.0, 100.0
mesh_line.width, mesh_line.height = 140.0, 100.0
mesh_to_points.width, mesh_to_points.height = 140.0, 100.0
instance_on_points.width, instance_on_points.height = 140.0, 100.0
collection_info.width, collection_info.height = 140.0, 100.0
scene_time.width, scene_time.height = 140.0, 100.0
math.width, math.height = 140.0, 100.0
realize_instances.width, realize_instances.height = 140.0, 100.0
#initialize obj_sequence_player links
#realize_instances.Geometry -> group_output.Geometry
obj_sequence_player.links.new(realize_instances.outputs[0], group_output.inputs[0])
#mesh_line.Mesh -> mesh_to_points.Mesh
obj_sequence_player.links.new(mesh_line.outputs[0], mesh_to_points.inputs[0])
#mesh_to_points.Points -> instance_on_points.Points
obj_sequence_player.links.new(mesh_to_points.outputs[0], instance_on_points.inputs[0])
#collection_info.Instances -> instance_on_points.Instance
obj_sequence_player.links.new(collection_info.outputs[0], instance_on_points.inputs[2])
#math.Value -> instance_on_points.Instance Index
obj_sequence_player.links.new(math.outputs[0], instance_on_points.inputs[4])
#instance_on_points.Instances -> realize_instances.Geometry
obj_sequence_player.links.new(instance_on_points.outputs[0], realize_instances.inputs[0])
#scene_time.Frame -> math.Value
obj_sequence_player.links.new(scene_time.outputs[1], math.inputs[0])
return obj_sequence_player
def apply_modifier_to_firstframe_objects(firstframe_collection, node_group):
# Apply Geometry Node Modifier to first frame objects
modifier_name = "ObjSequencePlayerModifier"
for obj in firstframe_collection.objects:
if modifier_name not in obj.modifiers:
geom_nodes_modifier = obj.modifiers.new(name=modifier_name, type='NODES')
geom_nodes_modifier.node_group = node_group
else:
print(f"Modifier '{modifier_name}' already exists on {obj.name}")
def main():
# Check if we have the correct number of arguments
if len(sys.argv) < 5:
print("Usage: blender -b --python script.py -- <input_directory> <output_path>")
sys.exit(1)
# Get input directory and output path from command line arguments
input_directory = sys.argv[sys.argv.index('--') + 1]
output_path = sys.argv[sys.argv.index('--') + 2]
# Ensure absolute paths
input_directory = os.path.abspath(input_directory)
output_path = os.path.abspath(output_path)
print(f"Input Directory: {input_directory}")
print(f"Output Path: {output_path}")
# Import OBJ sequence
firstframe_collection, animation_collection = import_obj_sequence(input_directory)
if firstframe_collection is None or animation_collection is None:
print("Failed to import OBJ sequence")
sys.exit(1)
# Create Obj Sequence Player node group
node_group = obj_sequence_player_node_group()
# Apply modifier to first frame objects
apply_modifier_to_firstframe_objects(firstframe_collection, node_group)
# Deselect all objects
bpy.ops.object.select_all(action='DESELECT')
# Ensure there is only one object in the collection and select it
if len(firstframe_collection.objects) == 1:
obj = firstframe_collection.objects[0]
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
# Set scene start and end frames
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = len(animation_collection.objects)
# Export to Alembic
try:
bpy.ops.wm.alembic_export(
filepath=output_path,
start=bpy.context.scene.frame_start,
end=bpy.context.scene.frame_end,
selected=True
)
print(f"Successfully exported '{obj.name}' to {output_path}")
except Exception as e:
print(f"Error exporting to Alembic: {e}")
sys.exit(1)
else:
print("Error: Collection 'firstframe' does not contain exactly one object.")
sys.exit(1)
if __name__ == "__main__":
main()
to use the script without opening blender
& "C:\Program Files\Blender Foundation\Blender 4.3\blender.exe" --background --python obj_sequence_to_abc.py -- "C:\sequence_folder" result.abc
claude.ai create manual import because from cmd we can’t use import_obj , i don’t know why
Could not enable OBJ import addon: Error: Add-on not loaded: "io_scene_obj", cause: No module named 'io_scene_obj'
The code import the first frame in a collection called: firstframe
and import all frames with duplicated first frame obj in a collection called animation and hide it.
and it check if these collections already exist and have objects or not
then it create obj sequence player as new node and apply geometry node modifier to the first frame with preselected node and collection and finally
the code select the first frame object and export the file to adc
i try to activate this:
visible_objects_only=True
But the hidden collection objects always exported
It’s really a lot to process. Can you please narrow issue down as much as possible?
Which issue? error of import obj or alembic export?
I guess both separately.